官术网_书友最值得收藏!

Controlling parallelism

We know that spawned goroutines will start executing as soon as possible and in a simultaneous fashion. However, there is an inherent risk involved when the said goroutines need to work on a common source that has a lower limit on the number of simultaneous tasks it can handle. This might cause the common source to significantly slow down or in some cases even fail. As you might guess, this is not a new problem in the field of computer science, and there are many ways to handle it. As we shall see throughout the chapter, Go provides mechanisms to control parallelism in a simple and intuitive fashion. Let's start by looking at an example to simulate the problem of burdened common source, and then proceed to solve it.

Imagine a cashier who has to process orders, but has a limit to process only 10 orders in a day. Let's look at how to present this as a program:

// cashier.go 
package main 
 
import ( 
    "fmt" 
    "sync" 
) 
 
func main() { 
    var wg sync.WaitGroup 
    // ordersProcessed & cashier are declared in main function 
    // so that cashier has access to shared state variable 'ordersProcessed'. 
    // If we were to declare the variable inside the 'cashier' function, 
    // then it's value would be set to zero with every function call. 
    ordersProcessed := 0 
    cashier := func(orderNum int) { 
        if ordersProcessed < 10 { 
            // Cashier is ready to serve! 
            fmt.Println("Processing order", orderNum) 
            ordersProcessed++ 
        } else { 
            // Cashier has reached the max capacity of processing orders. 
            fmt.Println("I am tired! I want to take rest!", orderNum) 
        } 
        wg.Done() 
    } 
 
    for i := 0; i < 30; i++ { 
        // Note that instead of wg.Add(60), we are instead adding 1 
        // per each loop iteration. Both are valid ways to add to WaitGroup as long as we can ensure the right number of calls. 
        wg.Add(1) 
        go func(orderNum int) { 
            // Making an order 
            cashier(orderNum) 
        }(i) 
 
    } 
    wg.Wait() 
} 

A possible output of the program might be as follows:

Processing order 29
Processing order 22
Processing order 23
Processing order 13
Processing order 24
Processing order 25
Processing order 21
Processing order 26
Processing order 0
Processing order 27
Processing order 14
I am tired! I want to take rest! 28
I am tired! I want to take rest! 1
I am tired! I want to take rest! 7
I am tired! I want to take rest! 8
I am tired! I want to take rest! 2
I am tired! I want to take rest! 15
...

The preceding output shows a cashier who was overwhelmed after taking 10 orders. However, an interesting point to note is that if you run the preceding code multiple times, you might get different outputs. For example, all of the 30 orders might be processed in one of the runs!

This is happening because of what is known as the race condition. A data race (or race condition) occurs when multiple actors (goroutines, in our case) are trying to access and modify a common shared state, and this results in incorrect reads and writes by the goroutines.

We can try to solve this issue in two ways:

  • Increasing the limit for processing orders
  • Increasing the number of cashiers

Increasing the limit is feasible only to a certain extent, beyond which it would start degrading the system or in the case of the cashier, work will neither be efficient nor 100% accurate. On the contrary, by increasing the number of cashiers, we can start processing more orders consecutively while not changing the limit. There are two approaches to this:

  • Distributed work without channels
  • Distributed work with channels
主站蜘蛛池模板: 聂荣县| 塔河县| 北票市| 福贡县| 保康县| 根河市| 成都市| 句容市| 西林县| 绵竹市| 阿克陶县| 云霄县| 海丰县| 自治县| 花莲县| 高碑店市| 辉县市| 岳池县| 绍兴市| 铅山县| 金门县| 九江县| 乌拉特后旗| 济南市| 长垣县| 乌兰察布市| 扎鲁特旗| 前郭尔| 丽江市| 菏泽市| 镶黄旗| 安宁市| 凤凰县| 陇川县| 西乌珠穆沁旗| 阿勒泰市| 宝兴县| 廊坊市| 名山县| 竹北市| 肥西县|