【问题标题】:Why variable output here with channels为什么在此处使用通道进行可变输出
【发布时间】:2019-08-06 12:31:46
【问题描述】:

我正在尝试从here 修改的代码。我创建 5 个通道并发送数据 5 次:

package main
import "fmt"
func greet(c chan string) {
    fmt.Println("Hello " + <-c + "!")
}
func main() {
    fmt.Println("main() started")
    c := make(chan string)
    for i:=0; i<5; i++ {
        go greet(c)
    }
    c <- "AAA"
    c <- "BBB"
    c <- "CCC"
    c <- "DDD"
    c <- "EEE"
    fmt.Println("main() stopped")
}

我希望打印所有 5 个字符串。但是,我发现可变输出。一些输出是:

$ ./rnchannel
main() started
Hello AAA!
Hello DDD!
Hello BBB!
Hello CCC!
Hello EEE!
main() stopped

$ ./rnchannel
main() started
Hello CCC!
Hello DDD!
main() stopped

$ ./rnchannel
main() started
Hello CCC!
Hello BBB!
Hello AAA!
Hello DDD!
main() stopped

为什么要打印可变数量的行?

【问题讨论】:

    标签: go channel


    【解决方案1】:

    您不会等到所有字符串都打印出来后才退出。一旦主线程到达执行结束,它就会关闭所有 goroutine 并结束程序。由于这同时发生,因此无法确定允许打印多少个字符串。

    【讨论】:

    • +1。到达程序结束保证您已写入频道 5 次。因为通道读写是阻塞的,这也意味着通道已经被读取了 5 次。但保证的是,所有 5 个读取值都已与“Hello”和“!”连接起来。字符串和Printlned 在程序退出之前。
    【解决方案2】:

    @AdamSmith identified the problem。当main() 退出时,所有的 goroutine 都会被杀死。不能保证您的其他 goroutine 会在此之前完成,这就是并发的本质。以下是您解决问题的方法。

    首先,让我们对greet 进行一些更改。让它睡一会儿以使问题更加明显。我们也会让它接受一个字符串而不是频道,我们马上就会知道为什么。

    func greet(str string) {
        time.Sleep(100 * time.Millisecond)
        fmt.Println("Hello " + str + "!")
    }
    

    与其创建一堆 goroutine 来从一个通道读取固定次数,我们想要一个从通道读取直到耗尽的 goroutine。使用range 最容易做到这一点。这充分利用了渠道。

    我们还需要一种方法来告诉主程序等到循环完成。 This is easiest done with a second channel。更复杂的同步使用WaitGroups

    c := make(chan string, 2)
    done := make(chan bool, 1)
    go func() {
        for str := range(c) {
            greet(str)
        }
        done <- true
    }()
    

    goroutine 将从c 读取直到它关闭。然后它将true发送到频道done。两者都被缓冲以避免由于阻塞等待读取或写入通道而导致死锁。

    回到main,我们写入通道,显式关闭它,然后等待从done读取。

        c <- "AAA"
        c <- "BBB"
        c <- "CCC"
        c <- "DDD"
        c <- "EEE"
        close(c)
    
        <-done
        fmt.Println("main() stopped")
    

    &lt;-done 会一直阻塞,直到有东西要读。这允许 goroutine 完成。

    并将它们整合在一起。

    package main
    import(
        "fmt"
        "time"
    )
    func greet(str string) {
        time.Sleep(100 * time.Millisecond)
        fmt.Println("Hello " + str + "!")
    }
    func main() {
        fmt.Println("main() started")
        c := make(chan string, 2)
        done := make(chan bool, 1)
        go func() {
            for str := range(c) {
                greet(str)
            }
            done <- true
        }()
        c <- "AAA"
        c <- "BBB"
        c <- "CCC"
        c <- "DDD"
        c <- "EEE"
        close(c)
    
        <-done
        fmt.Println("main() stopped")
    }
    

    【讨论】:

    • 解释得很好。
    • 为什么它在完成所有任务之前就结束了。有什么优势?
    • 为什么 Go 是这样设计的?我猜想确保 Go 程序可靠地停止。
    • 它是否禁止如下语句:'for {}' 将永远运行?
    • @mso 没有。循环比并发简单得多。
    猜你喜欢
    • 1970-01-01
    • 2020-06-17
    • 1970-01-01
    • 1970-01-01
    • 2011-06-12
    • 1970-01-01
    • 2018-01-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多