Golang -为什么使用done通道

0yycz8jy  于 2023-03-10  发布在  Go
关注(0)|答案(4)|浏览(214)

这是Golang的一个示例代码。但是不明白为什么在这种情况下需要“完成”通道。
https://gobyexample.com/closing-channels
没有理由将true发送到已完成通道。当打印“发送所有作业”消息时,我们可以知道作业通道已完成,不是吗?
我删除了与完成通道相关的代码,结果仍然相同。
https://play.golang.org/p/xOmzobFpTQ

p4tfgftt

p4tfgftt1#

不,结果是不一样的:
在很多情况下(例如,不同的CPU负载,以及它是不确定的和系统相关的行为),你的主goroutine会在你的received job goroutine之前退出,所以在这种情况下你不能保证all jobs received,例如just Add

time.Sleep(500)

之前

fmt.Println("received job", j)

要看到这一点,请在The 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

参见:
Goroutine does not execute if time.Sleep included
Why is time.sleep required to run certain goroutines?
Weird channel behavior in go

fjnneemd

fjnneemd2#

TL;DR:有一个竞态条件---你很幸运。
如果没有done通道,则程序的输出是不确定的。
根据线程的执行顺序,主线程可能会在goroutine完成处理之前退出,从而导致goroutine在中途被杀死。
通过强制主线程从done通道读取,我们强制主线程等待,直到done通道中有一些数据需要使用,这给了我们一个简洁的同步机制,goroutine通过向done通道写入来通知主线程它已经完成,这反过来又导致主线程's阻止<- done完成并导致程序终止。

vbopmzt1

vbopmzt13#

  • 我认为公认的答案并没有说明确切的原因 *

Go语言属于过程化范式,这意味着每条指令都是线性执行的,当一个Go例程从主Go例程分支出来时,它就开始了自己的小冒险,让主线程返回。
缓冲通道的容量为5,这意味着在缓冲区满之前它不会阻塞。如果缓冲区为空,它也会阻塞(容量为零的通道本质上是无缓冲的)。
由于只有4次迭代(0到〈=3),读操作将不会阻塞。
通过指示主线程从done通道读取数据,我们强制主线程等待,直到done通道中有一些数据需要使用。当迭代结束时,else分支被执行,写操作done <- true导致主线程中<- done读操作的释放。读操作等待从done中取出现在插入的值。
done阅读后,Go主例程不再被阻塞,因此成功终止。

ee7vknir

ee7vknir4#

  • 发送并不意味着工作完成,当工作需要很长时间才能完成。
  • 作业通道是缓冲的,因此即使发送了作业,工作线程也可能在此时没有接收到作业。

相关问题