What is the Advantage of sync.WaitGroup over Channels?

后端 未结 6 720
旧巷少年郎
旧巷少年郎 2021-01-30 10:33

I\'m working on a concurrent Go library, and I stumbled upon two distinct patterns of synchronization between goroutines whose results are similar:

Waitgroup



        
6条回答
  •  孤城傲影
    2021-01-30 10:52

    A WaitGroup's main advantage is simplicity.
    Channels can be buffered or unbuffered and carrying a message or just a signal (without a message - empty channel), so there are many different use cases, and

    "A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished."

    Let's do a benchmark:
    TLDR:
    Using sync.WaitGroup in the same code (same manner with done channel) is a little (as 9%) faster than buffered done channel (for the following benchmark):
    695 ns/op vs 758 ns/op.
    For unbuffered done channel, using sync.WaitGroupis faster (2x or more) - due to unbuffered channel synchronisation (for the following benchmark):
    722 ns/op vs 2343 ns/op.

    Benchmarks (using go version go1.14.7 linux/amd64):

    1. Using a buffered done channel:
    var done = make(chan struct{}, 1_000_000)
    

    With benchtime command:

    go test -benchtime=1000000x -benchmem -bench .
    # BenchmarkEvenWaitgroup-8         1000000               695 ns/op               4 B/op          0 allocs/op
    # BenchmarkEvenChannel-8           1000000               758 ns/op              50 B/op          0 allocs/op
    

    1. Using a unbuffered done channel:
    var done = make(chan struct{})
    

    With this command:

    go test -benchtime=1000000x -benchmem -bench .
    # BenchmarkEvenWaitgroup-8         1000000               722 ns/op               4 B/op          0 allocs/op
    # BenchmarkEvenChannel-8           1000000              2343 ns/op             520 B/op          1 allocs/op
    

    Code:

    package main
    
    import (
        "sync"
    )
    
    func main() {
        evenWaitgroup(8)
    }
    
    func waitgroup(n int) {
        select {
        case ch <- n: // tx if channel is empty
        case i := <-ch: // rx if channel is not empty
            // fmt.Println(n, i)
            _ = i
        }
        wg.Done()
    }
    
    func evenWaitgroup(n int) {
        if n%2 == 1 { // must be even
            n++
        }
        for i := 0; i < n; i++ {
            wg.Add(1)
            go waitgroup(i)
        }
        wg.Wait()
    }
    
    func channel(n int) {
        select {
        case ch <- n: // tx if channel is empty
        case i := <-ch: // rx if channel is not empty
            // fmt.Println(n, i)
            _ = i
        }
        done <- struct{}{}
    }
    
    func evenChannel(n int) {
        if n%2 == 1 { // must be even
            n++
        }
        for i := 0; i < n; i++ {
            go channel(i)
        }
        for i := 0; i < n; i++ {
            <-done
        }
    }
    
    var wg sync.WaitGroup
    var ch = make(chan int)
    var done = make(chan struct{}, 1000000)
    
    // var done = make(chan struct{})
    

    Note: Switch the comment for a buffered and unbuffered done channel benchmark:

    var done = make(chan struct{}, 1000000)
    // var done = make(chan struct{})
    

    main_test.go file:

    package main
    
    import (
        "testing"
    )
    
    func BenchmarkEvenWaitgroup(b *testing.B) {
        evenWaitgroup(b.N)
    }
    func BenchmarkEvenChannel(b *testing.B) {
        evenChannel(b.N)
    }
    

提交回复
热议问题