Go 中的通道阻塞是如何工作的?

Posted

技术标签:

【中文标题】Go 中的通道阻塞是如何工作的?【英文标题】:How does channel blocking work in Go? 【发布时间】:2015-12-08 21:34:02 【问题描述】:

我正在学习 Go 语言。这是我遇到的一个例子。有人可以解释一下这里发生了什么吗?

package main
import "time"
import "fmt"
func main() 
    c1 := make(chan string)
    c2 := make(chan string)
    go func() 
        time.Sleep(time.Second * 1)
        c1 <- "one"
    ()
    go func() 
        time.Sleep(time.Second * 2)
        c2 <- "two"
    ()
    for i := 0; i < 2; i++ 
      select 
        case msg1 := <-c1:
          fmt.Println("received", msg1)
        case msg2 := <-c2:
          fmt.Println("received", msg2)
        default:
          fmt.Println("Default")
      
    

输出:

Default
Default
Program Exited

如果我注释掉默认部分

//default:
//    fmt.Println("Default")

输出变成:

received one
received two
Program exited.

default 案例的存在如何改变通道阻塞的工作方式?

【问题讨论】:

【参考方案1】:

select 语句阻塞,直到至少一个案例准备好。 Go 语言规范reads, in part:

如果一个或多个通信可以继续,则通过统一的伪随机选择选择一个可以继续的通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况,“select”语句会阻塞,直到至少有一个通信可以继续。

在原始代码中,default 案例在循环的两次迭代中都准备就绪,因为在 c1c2 发送任何内容之前存在延迟。

删除default 大小写后,select 语句必须等待c1c2 中的数据可用。

【讨论】:

感谢您的澄清。 感谢您的支持。我有点恼火,因为我先发布了我的答案。 我选择了另一个答案,因为它包含了程序在 go 例程完成工作之前退出的原因。 我以为我在这里介绍了它:“在原始代码中,default 案例在循环的两次迭代中都已准备就绪,因为在 c1 或 @987654332 上发送任何内容之前存在延迟@。” @MichaelLaszlo 看到您的评论后,我取消了对已接受答案的投票,并为您投票。【参考方案2】:

这与 select 语句在 Go 中的工作方式有关。

来自the Go documentation on select

如果可以进行一项或多项通信,则一项 可以通过统一的伪随机选择来选择。 否则,如果存在默认情况,则选择该情况。如果有 不是默认情况,“select”语句阻塞,直到至少一个 的通信可以继续。

因此,在没有默认情况的情况下,代码将阻塞,直到任一通道中的某些数据可用。它隐式等待其他 goroutine 唤醒并写入它们的通道。

当你添加默认情况时,很可能在其他 goroutine 从睡眠中唤醒之前到达select 语句。

因此,由于(还)没有可用数据,并且存在默认情况,因此执行默认情况。此操作执行两次,所需时间不到 1 秒。所以程序在任何 go 例程有机会唤醒并写入通道之前终止。

请注意,这在技术上是一种竞争条件;绝对不能保证循环的 2 次迭代会在任何 go 例程唤醒之前运行,因此理论上即使在默认情况下也可能有不同的输出,但实际上这极不可能。

【讨论】:

【参考方案3】:

https://tour.golang.org/concurrency/5

https://tour.golang.org/concurrency/6

请参阅上面给出的链接以了解示例执行情况。如果没有其他案例准备好,则执行默认案例。 在 golang 块中选择,直到其中一个案例准备好。因此删除默认值使得其他情况的执行成为可能,否则它是在其他情况之前准备好的情况

【讨论】:

链接很有用,但如果您可以发布相关代码,就好像链接死了,您的答案将变得无用,谢谢。【参考方案4】:
Explanation:

c1 := make(chan string) // Creates a channel of type string. [Means only 
strings can be sent/received on this channel]

go func() 
        time.Sleep(time.Second * 1)
        c1 <- "one"
    ()
// func() is a goroutine [As go keyword is placed before, if no go keyword 
here, then it is a simple function].

time.Sleep(time.Second * 1) // here this func() goroutine will sleep for a 
second before passing a value to the channel c1.

c1 <- "one"// value "one" is passed to this channel.

select statement: Here it waits for the goroutines to complete it's task. 
Once a goroutine above finishes, it matches it's case and executes the 
statements. 

【讨论】:

这很没用

以上是关于Go 中的通道阻塞是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

Go笔记(十四):通道 channel

go语言中的并发

如何在处理结果时正确关闭 Goroutines 中的共享通道

Go非缓冲/缓冲/双向/单向通道

Go by Example 中文练习

Go by Example 中文练习