为什么要使用done信道(channel)?- Golang

6
这是Golang的示例代码之一。但在这种情况下,无法理解为什么需要使用“done”通道。

https://gobyexample.com/closing-channels

没有理由向完成通道发送true。当打印“发送所有作业”消息时,我们可以知道作业通道已完成,不是吗?

我删除了与完成通道相关的代码,结果仍然相同。

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

4个回答

8
不,结果并不相同:
在许多情况下(例如不同的CPU负载,这是不确定和系统依赖的行为),您的主goroutine在received job goroutine之前退出,因此您无法保证所有任务都已接收,例如仅添加。
time.Sleep(500)

之前

fmt.Println("received job", j)

要查看此内容,请在Go Playground上尝试:

// _Closing_ a channel indicates that no more values
// will be sent on it. This can be useful to communicate
// completion to the channel's receivers.

package main

import (
    "fmt"
    "time"
)

// In this example we'll use a `jobs` channel to
// communicate work to be done from the `main()` goroutine
// to a worker goroutine. When we have no more jobs for
// the worker we'll `close` the `jobs` channel.
func main() {
    jobs := make(chan int, 5)
    //done := make(chan bool)

    // Here's the worker goroutine. It repeatedly receives
    // from `jobs` with `j, more := <-jobs`. In this
    // special 2-value form of receive, the `more` value
    // will be `false` if `jobs` has been `close`d and all
    // values in the channel have already been received.
    // We use this to notify on `done` when we've worked
    // all our jobs.
    go func() {
        for {
            j, more := <-jobs
            if more {
                time.Sleep(500)
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                //done <- true
                return
            }
        }
    }()

    // This sends 3 jobs to the worker over the `jobs`
    // channel, then closes it.
    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)
    fmt.Println("sent all jobs")

    // We await the worker using the
    // [synchronization](channel-synchronization) approach
    // we saw earlier.
    //<-done
}

输出:

sent job 1
sent job 2
sent job 3
sent all jobs

替代方案:

sent job 1
received job 1
received job 2
sent job 2
sent job 3
received job 3
received all jobs
sent all jobs

请参考以下内容:
如果包含time.Sleep,则Goroutine不会执行
为什么运行某些goroutines需要time.sleep?
Go中奇怪的通道行为


5
TL;DR: 存在竞争条件——你很幸运。
如果没有“完成”通道,则程序的输出是不确定的。
取决于线程执行顺序,主线程可能会在goroutine完成处理之前退出,从而导致goroutine在中途被杀死。
通过强制主线程从“完成”通道读取,我们强制主线程等待直到有一些数据可以在“完成”通道中消耗。这为我们提供了一个简洁的同步机制,其中goroutine通过写入“完成”通道来通知主线程已经完成。这反过来导致主线程的阻塞<- done完成并导致程序终止。

3

我认为被接受的答案没有详细解释原因。


Go语言属于过程式编程范式,这意味着每条指令都是按顺序执行的。当一个Go协程从主Go协程中分离出来时,它就会进入自己的小冒险,并使主线程返回。

缓冲通道的容量为5,这意味着它只有在缓冲区满时才会阻塞。如果为空(具有零容量的通道本质上是未缓冲的),它也会阻塞。

由于只有4个迭代(0到<=3),因此读取操作不会阻塞。

通过指示主线程从done通道读取,我们强制主线程等待直到done通道中有一些数据可以消耗。当迭代结束时,将执行else分支并导致写操作done <- true释放主线程中的<- done读操作。读取操作等待将现在插入的值从done中拉出。

done读取后,主Go协程不再被阻塞,因此成功终止。


抱歉混淆了线程和 goroutine,它们并不相同! - Remario
所以基本上因为缓冲区容量充足,它没有被阻塞。 - Remario
缓冲通道仅在缓冲区已满时阻塞。当缓冲区为空时,接收方会被阻塞。 - Remario

1
  • 当任务需要很长时间才能完成时,发送并不意味着工作已经完成。

  • 作业通道是有缓冲区的,因此即使作业已经被发送,工作者可能在那个时候甚至还没有接收到该作业。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接