【问题标题】:Golang - Why use done channelGolang - 为什么使用完成通道
【发布时间】:2017-02-14 15:48:12
【问题描述】:

这是 Golang 的示例代码之一。但无法理解为什么在这种情况下需要“完成”频道。

https://gobyexample.com/closing-channels

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

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

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

【问题讨论】:

    标签: go goroutine


    【解决方案1】:

    不,结果不一样:
    在许多情况下(例如,不同的 CPU 负载,它是不确定的和系统相关的行为),您的主 goroutine 在您的 received job goroutine 之前退出,因此您不能保证 all jobs received,例如只需添加

    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

    【讨论】:

      【解决方案2】:

      TL;DR:有一个竞争条件---你很幸运。

      如果您没有done 频道,则程序的输出是不确定的。

      根据线程执行顺序,主线程可能会在 goroutine 完成处理之前退出,从而导致 goroutine 在中途被杀死。

      通过强制主线程从done 通道读取数据,我们强制主线程等待,直到done 通道中有一些数据要消耗。这为我们提供了一种简洁的同步机制,其中 goroutine 通过写入done 通道来通知主线程它已完成。这反过来会导致主线程的阻塞&lt;- done 完成并导致程序终止。

      【讨论】:

        【解决方案3】:

        我认为接受的答案没有详细说明原因。


        go 语言属于过程范式,意味着每条指令都是线性执行的。当一个 go 例程从主 go 例程派生出来时,它会开始自己的小冒险,让主线程返回。

        缓冲通道的容量为 5,这意味着它不会阻塞,直到缓冲区已满。如果它是空的,它也会阻塞(容量为零的通道本质上是无缓冲的)。

        由于只有4次迭代(0到

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

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

        【讨论】:

        • 抱歉混用线程和goroutine,它们不一样!
        • 所以基本上是因为缓冲区有足够的容量,它没有阻塞
        • 仅当缓冲区已满时才缓冲通道块。当缓冲区为空时接收块。
        【解决方案4】:
        • 发送并不意味着工作已经完成,当工作需要很长时间才能完成时 结束。

        • 作业通道被缓冲,因此即使发送作业,作业也可能 甚至还没有被工人收到。

        【讨论】:

          猜你喜欢
          • 2014-03-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-28
          相关资源
          最近更新 更多