sync.WaitGroup 在 Channels 上的优势是什么?

我正在开发一个并发 Go 库,我偶然发现了 goroutines 之间的两种不同的同步模式,它们的结果相似:




package main


import (

    "fmt"

    "sync"

    "time"

)


var wg sync.WaitGroup


func main() {

    words := []string{"foo", "bar", "baz"}


    for _, word := range words {

        wg.Add(1)

        go func(word string) {

            time.Sleep(1 * time.Second)

            defer wg.Done()

            fmt.Println(word)

        }(word)

    }

    // do concurrent things here


    // blocks/waits for waitgroup

    wg.Wait()

}



package main


import (

    "fmt"

    "time"

)


func main() {

    words := []string{"foo", "bar", "baz"}

    done := make(chan bool)

    // defer close(done)

    for _, word := range words {

        // fmt.Println(len(done), cap(done))

        go func(word string) {

            time.Sleep(1 * time.Second)

            fmt.Println(word)

            done <- true

        }(word)

    }

    // Do concurrent things here


    // This blocks and waits for signal from channel

    for range words {

        <-done

    }

}


我被告知它的sync.WaitGroup性能稍好一些,而且我已经看到它被普遍使用。但是,我发现频道更惯用。使用sync.WaitGroupover channel的真正优势是什么和/或当它更好时可能会是什么情况?


慕运维8079593
浏览 204回答 3
3回答

慕标5832272

独立于第二个示例的正确性(如评论中所述,您没有按照自己的想法行事,但很容易修复),我倾向于认为第一个示例更容易掌握。现在,我什至不会说频道更惯用了。通道是 Go 语言的一个标志性特性,并不意味着尽可能地使用它们是惯用的。Go 中的惯用语是使用最简单、最容易理解的解决方案:在这里,WaitGroup传递含义(您的主要功能是Wait让工人完成)和机制(工人通知他们何时完成Done)。除非您处于非常特殊的情况,否则我不建议在此处使用通道解决方案。

杨魅力

这取决于用例。如果您要分派一次性作业以并行运行而无需知道每个作业的结果,那么您可以使用WaitGroup.&nbsp;但是如果你需要从 goroutines 收集结果,那么你应该使用一个通道。由于通道双向工作,我几乎总是使用通道。另一方面,正如评论中所指出的,您的频道示例未正确实现。您需要一个单独的通道来指示没有更多的工作要做(一个例子是这里)。在您的情况下,由于您事先知道字数,因此您可以只使用一个缓冲通道并接收固定次数以避免声明关闭通道。

ABOUTYOU

对于您的简单示例(表示作业完成),这WaitGroup是显而易见的选择。Go 编译器非常友好,不会怪你使用通道来发送简单的完成任务信号,但一些代码审查者会这样做。"WaitGroup 等待一组 goroutines 完成。主 goroutine 调用Add(n)设置要等待的 goroutines 的数量。然后每个 goroutines 运行并Done()在完成时调用。同时,Wait 可用于阻塞直到所有goroutine 已经完成了。”words := []string{"foo", "bar", "baz"}var wg sync.WaitGroupfor _, word := range words {&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go func(word string) {&nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(100 * time.Millisecond) // a job&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(word)&nbsp; &nbsp; }(word)}wg.Wait()可能性仅受您的想象力的限制:通道可以被缓冲:words := []string{"foo", "bar", "baz"}done := make(chan struct{}, len(words))for _, word := range words {&nbsp; &nbsp; go func(word string) {&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(100 * time.Millisecond) // a job&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(word)&nbsp; &nbsp; &nbsp; &nbsp; done <- struct{}{} // not blocking&nbsp; &nbsp; }(word)}for range words {&nbsp; &nbsp; <-done}通道可以是无缓冲的,你可以只使用一个信号通道(例如chan struct{}):words := []string{"foo", "bar", "baz"}done := make(chan struct{})for _, word := range words {&nbsp; &nbsp; go func(word string) {&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(100 * time.Millisecond) // a job&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(word)&nbsp; &nbsp; &nbsp; &nbsp; done <- struct{}{} // blocking&nbsp; &nbsp; }(word)}for range words {&nbsp; &nbsp; <-done}您可以限制具有缓冲通道容量的并发作业数:t0 := time.Now()var wg sync.WaitGroupwords := []string{"foo", "bar", "baz"}done := make(chan struct{}, 1) // set the number of concurrent job herefor _, word := range words {&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go func(word string) {&nbsp; &nbsp; &nbsp; &nbsp; done <- struct{}{}&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(100 * time.Millisecond) // job&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(word, time.Since(t0))&nbsp; &nbsp; &nbsp; &nbsp; <-done&nbsp; &nbsp; &nbsp; &nbsp; wg.Done()&nbsp; &nbsp; }(word)}wg.Wait()您可以使用频道发送消息:done := make(chan string)go func() {&nbsp; &nbsp; for _, word := range []string{"foo", "bar", "baz"} {&nbsp; &nbsp; &nbsp; &nbsp; done <- word&nbsp; &nbsp; }&nbsp; &nbsp; close(done)}()for word := range done {&nbsp; &nbsp; fmt.Println(word)}基准:&nbsp; &nbsp; go test -benchmem -bench . -args -n 0# BenchmarkEvenWaitgroup-8&nbsp; 1827517&nbsp; &nbsp;652 ns/op&nbsp; &nbsp; 0 B/op&nbsp; 0 allocs/op# BenchmarkEvenChannel-8&nbsp; &nbsp; 1000000&nbsp; 2373 ns/op&nbsp; 520 B/op&nbsp; 1 allocs/op&nbsp; &nbsp; go test -benchmem -bench .# BenchmarkEvenWaitgroup-8&nbsp; 1770260&nbsp; &nbsp;678 ns/op&nbsp; &nbsp; 0 B/op&nbsp; 0 allocs/op# BenchmarkEvenChannel-8&nbsp; &nbsp; 1560124&nbsp; 1249 ns/op&nbsp; 158 B/op&nbsp; 0 allocs/op代码( main_test.go):package mainimport (&nbsp; &nbsp; "flag"&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "os"&nbsp; &nbsp; "sync"&nbsp; &nbsp; "testing")func BenchmarkEvenWaitgroup(b *testing.B) {&nbsp; &nbsp; evenWaitgroup(b.N)}func BenchmarkEvenChannel(b *testing.B) {&nbsp; &nbsp; evenChannel(b.N)}func evenWaitgroup(n int) {&nbsp; &nbsp; if n%2 == 1 { // make it even:&nbsp; &nbsp; &nbsp; &nbsp; n++&nbsp; &nbsp; }&nbsp; &nbsp; for i := 0; i < n; i++ {&nbsp; &nbsp; &nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; &nbsp; &nbsp; go func(n int) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case ch <- n: // tx if channel is empty&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case i := <-ch: // rx if channel is not empty&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // fmt.Println(n, i)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ = i&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; }(i)&nbsp; &nbsp; }&nbsp; &nbsp; wg.Wait()}func evenChannel(n int) {&nbsp; &nbsp; if n%2 == 1 { // make it even:&nbsp; &nbsp; &nbsp; &nbsp; n++&nbsp; &nbsp; }&nbsp; &nbsp; for i := 0; i < n; i++ {&nbsp; &nbsp; &nbsp; &nbsp; go func(n int) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case ch <- n: // tx if channel is empty&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case i := <-ch: // rx if channel is not empty&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // fmt.Println(n, i)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ = i&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; done <- struct{}{}&nbsp; &nbsp; &nbsp; &nbsp; }(i)&nbsp; &nbsp; }&nbsp; &nbsp; for i := 0; i < n; i++ {&nbsp; &nbsp; &nbsp; &nbsp; <-done&nbsp; &nbsp; }}func TestMain(m *testing.M) {&nbsp; &nbsp; var n int // We use TestMain to set up the done channel.&nbsp; &nbsp; flag.IntVar(&n, "n", 1_000_000, "chan cap")&nbsp; &nbsp; flag.Parse()&nbsp; &nbsp; done = make(chan struct{}, n)&nbsp; &nbsp; fmt.Println("n=", n)&nbsp; &nbsp; os.Exit(m.Run())}var (&nbsp; &nbsp; done chan struct{}&nbsp; &nbsp; ch&nbsp; &nbsp;= make(chan int)&nbsp; &nbsp; wg&nbsp; &nbsp;sync.WaitGroup)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go