与 WaitGroup 的通道同步。关闭通道和 Waitgroup

Channel synchronisation with WaitGroup. Closing channel and Waitgroup

我想了解 Goroutine 中的同步。我这里有一个代码,它在一个通道上写入从 0 到 4 的数字,完成后我使用 range 从通道读取并打印值。

下面的代码在我等待使用 wg.Wait() 并在单独的 Goroutine 中关闭通道时工作正常。

package main

import (
    "fmt"
    "strconv"
    "sync"
)

func putvalue(i chan string, value string, wg *sync.WaitGroup) {
    i <- value
    defer wg.Done()
}

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

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go putvalue(queue, strconv.Itoa(i), &wg)
    }

    go func() {
        wg.Wait()
        close(queue)
    }()

    for elem := range queue {
        fmt.Println(elem)
    }

}

https://play.golang.org/p/OtaRP3Mm4lk

但如果我使用完全相同的代码,但在主线程中等待并关闭通道,则会导致死锁。以下代码导致死锁。

package main

import (
    "fmt"
    "strconv"
    "sync"
)

func putvalue(i chan string, value string, wg *sync.WaitGroup) {
    i <- value
    defer wg.Done()
}

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

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go putvalue(queue, strconv.Itoa(i), &wg)
    }

    wg.Wait()
    close(queue)

    for elem := range queue {
        fmt.Println(elem)
    }

}

https://play.golang.org/p/JXmdsdPKQPu

据我所知,在第二种情况下,主线程执行停止并等待,但这与在单独的 goroutine 中执行有何不同?请帮助我理解这一点。

在第一个程序中,主 goroutine 创建了几个 goroutine,每个 goroutine 开始等待通道写入。然后主 goroutine 创建另一个等待 wg.Wait() 的 goroutine。主 goroutine 继续,从通道中读取,这也将所有 goroutines 一个接一个地启用,然后它们终止,释放等待 wg.Wait().

的 goroutine

在第二个程序中,您再次创建等待通道写入的 goroutine,但这一次,主 goroutine 调用 wg.Wait()。此时,你创建的所有goroutines都在等待channel变得可写,主goroutine在等待goroutines结束,这意味着死锁

将每个 goroutine 视为一个独立的人(或 gopher:https://blog.golang.org/gopher)可能会有所帮助。当你 go f() 你得到一个新的 person/gopher 并给他们 运行 宁功能的工作。所以,你有 5 个额外的 gophers 运行宁此:

func putvalue(i chan string, value string, wg *sync.WaitGroup) {
    i <- value
    defer wg.Done()
}

5 个 运行 中的每一个都到达了 i <- value 行,然后他们停下来,等待地鼠 运行 直到 "get"频道/邮箱的那一边,把手伸过去得到一个绳子,就是那种进入i频道/邮箱的包裹。

(另外:defer wg.Done() 应该是函数的 第一行 行,而不是最后一行。或者,只需将 wg.Done() 作为最后一行的功能。)

现在,如果此时您获得了第六只额外的地鼠并让他这样做:

{
    wg.Wait()
    close(queue)
}

他会在 wg.Wait() 里面停下来,等待。

您的主要 gopher 现在继续 for 循环。这是从邮箱中读取的,也就是说,现在你的 main gopher 把手伸进频道 "get" 一侧的 mailbox/window。五只等待中的地鼠中的一只终于可以将绳子交到您的地鼠手中了。您的主要 Gopher 获取字符串并将其带回您的 for 循环。被阻止的五个地鼠之一现在可以执行他的 wg.Done() 并过期(大概是去退休地鼠的幸福之地)。

您的主要 gopher 在 for 循环中继续,通过邮箱获取更多包裹。当他这样做时,等待邮箱 "put" 的四只地鼠结束并调用 wg.Done(),工作组倒计时。当计数为零时,不再有 gophers 等待将包裹放入邮箱,但是 现在 睡着了等待 wg.Wait() 的 gophers 被唤醒。所以他很快就会醒来并打电话给 close.

如果他还没有打电话给 close,那么您的主 gopher 会卡住等待下一个包裹。所以只有剩下的一只地鼠可以做任何事情:他会完成收盘。或者,也许他醒得很快并且已经关闭了,但如果是这样,您的主要 gopher 已经看到邮箱 window 永远关闭了。无论哪种方式,您的主 gopher 将已经看到或即将看到邮箱 window(通道)已关闭,并且 for 循环将停止并且您的主 gopher 将 return 从 main 出发,然后前往快乐退休之地。

一样,虽然没有单独的 gopher 执行 wg.Wait() 后跟 close,但您的 main gopher 正在执行wg.Wait()。所以他从来没有绕过 for 循环从(仍然打开的)邮箱/频道中读取。你的五只地鼠正在睡觉,等待一只地鼠把手伸进邮箱去取他们的包裹。您的主 gopher 正在睡觉,等待 sync.WaitGroup 中的计数器归零。大家都睡了!