等待 go routines 完成然后从通道读取

Wait for go routines to finish then read from channel

如何等待所有 go 例程完成,然后从通道读取所有数据?

为什么此示例在等待 go 例程完成时卡住?

Go Playground

package main

import (
    "fmt"
    "sync"
    "time"
)

func doStuff(i int, wg *sync.WaitGroup, messages chan<- string) {
    defer wg.Done()
    time.Sleep(time.Duration(i) * time.Second)
    messages <- fmt.Sprintf("Doing stuff...%d", i)

}

func doMoreStuff(i int, wg *sync.WaitGroup, messages chan<- string) {
    defer wg.Done()
    time.Sleep(time.Duration(i) * time.Second)
    messages <- fmt.Sprintf("Doing more stuff...%d", i)
}

func main() {
    var wg sync.WaitGroup
    var messages = make(chan string)

    for start := time.Now();; {
        elapsedTime := time.Since(start)
        fmt.Println(elapsedTime)
        if elapsedTime > time.Duration(3) * time.Second {
            fmt.Println("BREAK")
            break
        }

        for i := 0; i < 10; i++ {
            wg.Add(1)
            go doStuff(i, &wg, messages)

            wg.Add(1)
            go doMoreStuff(i, &wg, messages)
        }
        time.Sleep(time.Duration(1) * time.Second)
    }
    fmt.Println("WAITING")
    wg.Wait()
    fmt.Println("DONE")
    for message := range messages {
        fmt.Println(message)
    }
}

如果你想等待所有 goroutines 完成在通道上发送消息,然后你想开始读取消息,那么你别无选择,只能使用缓冲通道 "host" goroutines 可以发送的所有消息。

这不实用。即使你沿着这条路走下去,你也能够接收和打印消息,但是这样做的 for range 循环永远不会终止,因为它只会在收到通道上发送的所有消息时终止在它关闭之前,但你永远不会关闭通道。您可以在 Go Playground.

上查看此 "half-working" 解决方案

改为启动等待其他人完成的 goroutine,然后关闭通道:

go func() {
    fmt.Println("WAITING")
    wg.Wait()
    close(messages)
}()

所以现在你可以使用 for rangemain goroutine 中接收消息:

for message := range messages {
    fmt.Println(message)
}

fmt.Println("DONE")

Go Playground 上试试这个。

这个解决方案仍然不完美:它首先必须启动所有 goroutines,然后才尝试接收值,所有启动的 goroutines 将被阻止发送操作(直到 main 准备就绪收到那些)。

更好的解决方案可能是启动一个 goroutine 来接收值,最好是在 goroutines 在通道上发送消息之前启动(否则它们无论如何都会在发送时被阻止):

go func() {
    for message := range messages {
        fmt.Println(message)
    }
}()

并等待 goroutines 并在 main() 结束时关闭通道:

fmt.Println("WAITING")
wg.Wait()
close(messages)

问题在于,当 main() 结束时,您的应用程序也会结束,它不会等待其他非 main goroutines 完成。这意味着它不会等待 "consumer" goroutine 接收消息。

要等待 "consumer",您可以使用额外的 sync.WaitGroup:

var wg2 sync.WaitGroup
wg2.Add(1)
go func() {
    defer wg2.Done()
    for message := range messages {
        fmt.Println(message)
    }
}()

// And the end of `main()`:

fmt.Println("WAITING")
wg.Wait()
close(messages)

fmt.Println("DONE")
wg2.Wait()

Go Playground 上试试这个。

示例卡住,因为您使用的是无缓冲通道。两个 go-routines 都被阻塞等待写入通道,因为没有任何东西可以从它读取。

您可以使用缓冲通道,但您只能将其用作数据存储。渠道对于沟通更有用。问题是为什么要等到所有写手都写完了?在未知(无限)数量的作者的一般情况下,您将不知道要使您的频道有多大。