Methods for implementing concurrency control in go language: 1. WaitGroup, the task processing of multiple goroutines has a dependency or splicing relationship; 2. Channel, you can actively cancel goroutines; transfer data in multiple groutines; instead of the work of WaitGroup, Meet the functions of Context; 3. Context, signal propagation between multi-level groutines, including metadata propagation, cancellation of signal propagation, timeout control, etc.
#The operating environment of this article: Windows 10 system, go1.20 version, dell g3 computer.
In Golang, you can open a goroutine through the go keyword, so you can easily write concurrent code in Go. But how to effectively control these concurrently executing groutines?
When it comes to concurrency control, many people may first think of locks. Golang also provides lock-related mechanisms, including mutex lock sync.Mutex and read-write lock sync.RWMutex. In addition to locks, there are also atomic operations sync/atomic, etc. However, the focus of these mechanisms is the concurrent data safety of goroutines. What this article wants to discuss is the concurrency behavior control of goroutine.
In goroutine concurrent behavior control, there are three common methods, namely WaitGroup, channel and Context.
WaitGroup is located under the sync package. Its usage is as follows.
func main() { var wg sync.WaitGroup wg.Add(2) //添加需要完成的工作量2 go func() { wg.Done() //完成工作量1 fmt.Println("goroutine 1 完成工作!") }() go func() { wg.Done() //完成工作量1 fmt.Println("goroutine 2 完成工作!") }() wg.Wait() //等待工作量2均完成 fmt.Println("所有的goroutine均已完成工作!")}输出: //goroutine 2 完成工作! //goroutine 1 完成工作! //所有的goroutine均已完成工作!
WaitGroup This concurrency control method is especially suitable for: a task requires multiple goroutines to work together. Each goroutine can only do part of the task. The task is completed only when all goroutines are completed. Therefore, WaitGroup has the same meaning as its name, which is a way of waiting.
However, in actual business, there is such a scenario: when a certain requirement is met, a certain goroutine needs to be actively notified to end. For example, if we start a background monitoring goroutine, when monitoring is no longer needed, we should notify the monitoring goroutine to end, otherwise it will keep idling and cause leaks.
For the above scenario, WaitGroup can do nothing. The simplest method that can be thought of: define a global variable and notify it by modifying this variable elsewhere. The background goroutine will constantly check this variable. If it finds that the variable has changed, it will close it by itself. However, this method is somewhat cumbersome. clumsy. In this case, channel select can come in handy.
func main() { exit := make(chan bool) go func() { for { select { case <-exit: fmt.Println("退出监控") return default: fmt.Println("监控中") time.Sleep(2 * time.Second) } } }() time.Sleep(5 * time.Second) fmt.Println("通知监控退出") exit <- true //防止main goroutine过早退出 time.Sleep(5 * time.Second)}输出: //监控中 //监控中 //监控中 //通知监控退出 //退出监控
This combination of channel select is a more elegant way to notify the goroutine of the end.
However, this solution also has limitations. Just imagine, what if there are multiple goroutines that all need to be controlled to end? What if these goroutines spawn other goroutines? Of course we can define many channels to solve this problem, but the relationship chain of goroutines leads to the complexity of this scenario.
The above scenarios are common under the CS architecture model. In Go, a separate goroutine (A) is often opened for each client to handle its series of requests, and often a single A will also request other services (start another goroutine B), and B may also request another goroutine. C, C then sends the request to the server of Databse, for example. Imagine that when the client disconnects, A, B, and C associated with it need to exit immediately before the system can reclaim the resources occupied by A, B, and C. Exiting A is simple, but how to notify B and C to also exit?
At this time, Context appears.
func A(ctx context.Context, name string) { go B(ctx ,name) //A调用了B for { select { case <-ctx.Done(): fmt.Println(name, "A退出") return default: fmt.Println(name, "A do something") time.Sleep(2 * time.Second) } }}func B(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Println(name, "B退出") return default: fmt.Println(name, "B do something") time.Sleep(2 * time.Second) } }}func main() { ctx, cancel := context.WithCancel(context.Background()) go A(ctx, "【请求1】") //模拟client来了1个连接请求 time.Sleep(3 * time.Second) fmt.Println("client断开连接,通知对应处理client请求的A,B退出") cancel() //假设满足某条件client断开了连接,那么就传播取消信号,ctx.Done()中得到取消信号 time.Sleep(3 * time.Second)}输出: //【请求1】 A do something //【请求1】 B do something //【请求1】 A do something //【请求1】 B do something //client断开连接,通知对应处理client请求的A,B退出 //【请求1】 B退出 //【请求1】 A退出
In the example, a connection request is simulated from the client, and Goroutine A is opened accordingly for processing. A also enables B processing. Both A and B use Context for tracking. When we use the cancel function to notify the cancellation , these two goroutines will be terminated.
This is the control capability of Context. It is like a controller. After pressing the switch, all sub-Contexts based on this Context or derived from it will receive notifications. At this time, cleanup operations can be performed. Finally, Release goroutine, which elegantly solves the problem of uncontrollable goroutine after startup.
The detailed usage of Context is beyond the scope of this article. There will be a follow-up article specifically explaining the Context package, so stay tuned.
This article lists three concurrency behavior control modes in Golang. There is no good or bad distinction between modes, it just depends on using appropriate solutions for different scenarios. In actual projects, multiple methods are often used in combination.
In Golang, you can open a goroutine through the go keyword, so you can easily write concurrent code in Go. But how to effectively control these concurrently executing groutines?
When it comes to concurrency control, many people may first think of locks. Golang also provides lock-related mechanisms, including mutex lock sync.Mutex and read-write lock sync.RWMutex. In addition to locks, there are also atomic operations sync/atomic, etc. However, the focus of these mechanisms is the concurrent data safety of goroutines. What this article wants to discuss is the concurrency behavior control of goroutine.
In goroutine concurrent behavior control, there are three common methods, namely WaitGroup, channel and Context.
The above is the detailed content of How to implement concurrency control in go language. For more information, please follow other related articles on the PHP Chinese website!