SoFunction
Updated on 2025-03-01

Go concurrency: Use to implement coroutine synchronization method

I often see people asking how to wait for the coroutine created in the main coroutine to be executed before ending the main coroutine, for example, the following code:

package main
import (
    "fmt"
)
func main() {
    go func() {
        ("Goroutine 1")
    }()
    go func() {
        ("Goroutine 2")
    }()
}

Execute the above code and it is very likely that the output will not be seen, because it is possible that the two coroutines have not yet been executed, and all other coroutines will be terminated when the main coroutine ends.

The solution is to add a wait at the end of the main function:

package main
import (
    "fmt"
    "time"
)
func main() {
    go func() {
        ("Goroutine 1")
    }()
    go func() {
        ("Goroutine 2")
    }()
    ( * 1) // Sleep for 1 second, waiting for the above two coroutines to end}

This is not a perfect solution. If these two coroutines contain complex operations, it may be time-consuming and you cannot determine how long you need to sleep. Of course, you can use pipelines to achieve synchronization:

package main
import (
    "fmt"
)
func main() {
    ch := make(chan struct{})
    count := 2 // count represents the number of active coroutines    go func() {
        ("Goroutine 1")
        ch <- struct{}{} // The coroutine ends and sends a signal    }()
    go func() {
        ("Goroutine 2")
        ch <- struct{}{} // The coroutine ends and sends a signal    }()
    for range ch {
        // Each time you receive data from ch, it indicates that an active coroutine ends        count--
        // Close the pipeline when all active coroutines are finished        if count == 0 {
            close(ch)
        }
    }
}

The above solution is a relatively perfect solution, but Go provides a simpler way to use.

As the name suggests, WaitGroup is used to wait for a set of operations to complete.

WaitGroup implements a counter to record the number of unfinished operations. It provides three methods, Add() is used to add counts.

Done() is used to call at the end of the operation to decrement the count by one.

Wait() is used to wait for all operations to end, that is, the count becomes 0. The function will wait when the count is not 0 and will return immediately when the count is 0.

package main
import (
    "fmt"
    "sync"
)
func main() {
    var wg 
    (2) // Because there are two actions, add 2 counts    go func() {
        ("Goroutine 1")
        () // The operation is completed, reduce one count    }()
    go func() {
        ("Goroutine 2")
        () // The operation is completed, reduce one count    }()
    () // Wait until the count is 0}

It can be seen that it is the easiest way to use it.

Supplement: The little tricks of using WaitGroup in Golang

It is no stranger to Golang developers, and it is often used as a mechanism for synchronization between multiple coroutines. Using it well will definitely make you achieve twice the result with half the effort, but if you use it incorrectly, it will cause problems.

There are many examples about the use of WaitGroup on the Internet, and I won't introduce it here. What I want to say is the pitfalls I encountered when using WaitGroup in my project.

In the project, because the server has synchronization requirements, WaitGroup was used directly, but the usage scenario was not considered. As a result, after the project was launched, the client often stuttered during peak periods. After searching from multiple parties, it was found that if a separate goroutine was not started when using WaitGroup, it is very likely that the main thread would be blocked.

So I did the following test (in the test, I put WaitGroup inside the coroutine):

import (
 "fmt"
 "sync"
 "time"
)
 
func main() {
    ("main-1")
 testW()
 ("main-2")
 ((15) * ) 
}
 
func testW() {
 ("testW-1")
 go func() {
  var wg 
  ("testW-2")
  testW1(&wg)
  ("testW-5")
  ()
  ("testW-6")
 }()
}
 
func testW1(wg *) {
 (1)
 ("testW-3")
 (*5, func() {
  ()
 })
 ("testW-4") 
}

The output is:

main-1

testchan-1

main-2

testchan-2

testchan-3

testchan-4

testchan-5

// 5 seconds later

testchan-6

Summarize:

Using WaitGroup in goroutine will not cause blockage of the main thread, and can also achieve synchronization effect.

The above is personal experience. I hope you can give you a reference and I hope you can support me more. If there are any mistakes or no complete considerations, I would like to give you advice.