When learning Go, you know that Go language supports concurrency. The easiest way is to enable goroutine through the go keyword. But in work, we use the WaitGroup of the sync package, but this is not enough. When multiple goroutines access a variable at the same time, we must also consider how to ensure that these goroutines will not affect each other. This uses sync's Mutex again. How are they strung together?
1. Goroutinue
Let’s talk about goroutine first, we all know that it is a lightweight thread in Go. The Go program starts with the main() function of the main package. When the program starts, the Go program will create a default goroutine for the main() function. Use goroutine, use the keyword go.
package main import ( "fmt" ) func main() { // Concurrent execution of the program go running() } func running() { ("Goroutine") }
Execute the code and find that there is no "Goroutine" output we expected. This is because the current program is a single-threaded program. As long as the main function is executed, it will no longer care about what other threads are doing, and the program will automatically exit. The solution is to add a sleep function and let the main function wait for the running function to be executed before exiting. We assume that the code in the running function takes 2 seconds to execute, so let the main function wait for 3 seconds before exiting.
package main import ( "fmt" "time" ) func main() { // Concurrent execution of the program go running() (3 * ) } func running() { ("Goroutine") }
Execute the code again and the terminal outputs the "Goroutine" string we want.
2. WaitGroup
As afore, we assumed that the execution of the running function takes 2 seconds, but if it takes 10 seconds or even longer, I don’t know when the goroutin ends, do I need the main function sleep to have more seconds? You can't let the running function notify the main function after executing. Can the main function automatically exit when it receives the signal? It's really OK! You can use the Waitgroup of the sync package to determine whether a set of tasks is completed.
WatiGroup can wait until all goroutine executions are completed and block the main thread's execution until all goroutine executions are completed. It has 3 methods:
- Add(): Add the number of waiting goroutines to the counter.
- Done(): Decreases the value of the WaitGroup counter and should be executed at the end of the coroutine.
- Wait(): Execute blocking until all WaitGroup counts become 0
A simple example is as follows:
package main import ( "fmt” "sync” “time" ) func process(i int, wg *) { ("started Goroutine ", i) (2 * ) ("Goroutine %d ended\n", i) () } func main() { var wg for i := 0; i < 3; i++ { (1) go process(i, &wg) } () ("All go routines finished executing”) } //The main function can also be written in the following wayfunc main() { var wg (3) //Set the counter, the value is the number of goroutines go process(1, &wg) go process(2, &wg) go process(3, &wg) () //The main goroutine blocks waiting for the counter to change to 0 ("All goroutines finished executing") }
The command line output is as follows:
deer@192src % go run //The first time
started Goroutine 3
started Goroutine 1
started Goroutine 2
Goroutine 2 ended
Goroutine 1 ended
Goroutine 3 ended
All goroutines finished executingdeer@192src % go run //The second time
started Goroutine 3
started Goroutine 1
started Goroutine 2
Goroutine 1 ended
Goroutine 2 ended
Goroutine 3 ended
All goroutines finished executingdeer@192src % go run //The third time
started Goroutine 3
started Goroutine 2
started Goroutine 1
Goroutine 3 ended
Goroutine 1 ended
Goroutine 2 ended
All goroutines finished executing
Simply put, in the above program, wg maintains an internal counter and activates 3 goroutines:
1) Before each goroutine is activated, the Add() method is called to increase the goroutine count that needs to be waited for.
2) Each goroutine runs the process() function. When this function is completed, the Done() method needs to be called to represent the end of the goroutine.
3) After activation of 3 goroutines, the main goroutine will be executed to Wait(). Since each activated goroutine runs process() requires 2 seconds to sleep, the main goroutine will block for a period of time in Wait() (about 2 seconds).
4) When all goroutines are completed, the counter is reduced to 0, Wait() will no longer block, so the main goroutine can execute the following Println().
Note here:
1) Use pointer type in process()*
As a parameter, it means that these 3 goroutines share a wg, so that they know that all 3 goroutines are completed. If the value type is used hereAs a parameter, it means that each goroutine will copy a copy of wg, and each goroutine uses its own wg. The main goroutine will permanently block and cause deadlock.
2) The number of settings of Add() must be consistent with the actual number of goroutines waiting, that is, the number of calls to Done must be equal, otherwise panic will be performed, and the error message will be reported as follows:
fatal error: all goroutines are asleep - deadlock!
3. Lock
When multiple goroutines operate a variable at the same time, there will be data competition, resulting in the final result that does not match the expectations. The solution is to add a lock. The sync package in Go implements two types of locks: Mutex and RWMutex, the former is a mutex lock and the latter is a read-write lock, which is implemented based on Mutex. When our scenario is mainly written, you can use Mutex to lock and unlock.
var lock //Declare a mutex lock () // Add lock//code... () //Unlock
A mutex lock is actually that every thread tries to lock the resource before operating it. Only after successfully adding the lock can it be operated, and then unlocked after the operation is completed. That is to say, when using a mutex, only one goroutine can be executed at the same time.
The above is the detailed content of analyzing Go's Waitgroup and lock issues. For more information about Go's Waitgroup and locks, please follow my other related articles!