【问题标题】:Go channel write ordering guaranteesGo 通道写入顺序保证
【发布时间】:2021-11-26 03:58:57
【问题描述】:

给定以下代码:

ch1 := make(chan struct{}, 1)
ch2 := make(chan struct{})
ready := make(chan struct{})
done := make(chan struct{})

go func() {
    close(ready)
    select {
    case <-ch1:
        fmt.Println("ch1")
    case <-ch2:
        fmr.Println("ch2")
    }
}()

<-ready

ch1 <- struct{}{}
close(ch2)

<-done

是否保证总是打印“ch1”?或者有没有可能因为 ch1 被缓冲并且 ch2 被关闭之后,第二个 case 可以先运行?

在文档/代码中是否有任何参考来验证此行为?

【问题讨论】:

  • 相关参考用于选择语句:golang.org/ref/spec#Select_statements,其中 ” 如果一个或多个通信可以继续,则通过统一的伪随机选择选择一个可以继续的通信。”。频道是按顺序“写入”的,但您关心的是先接收哪个频道。

标签: go concurrency


【解决方案1】:

不,不能保证不会发生第二种情况。

当你使用通道时——通道是同步两个 goroutine 的理想选择——你正在操作两个独立的通道。因此,不能保证主 goroutine 对无缓冲 ch1 通道的写入会立即将控制权交给第二个 goroutine 的 select 语句。

ch2 的关闭也可能发生在调度程序将控制权交给其他 goroutine 之前 - 然后将随机选择具有两条可能路径的 select

Anectodal evidence 可能很容易让人相信,但会导致在不同负载条件下的危险假设。


查看Go Scheduler Design doc 的早期文档不会有太大帮助,因为时间表的实施细节实际上会随着Go 语言的发布而改变。

最后,需要两个独立通道之间的协调来保证处理顺序。

【讨论】:

  • 如果我确保 goroutine 已经在运行 before 我执行通道写入/关闭怎么办?这会有所作为吗?例如,通过使用在 goroutine 启动后完成的等待组?
  • @shmth 没关系。无法保证在写入ch1select 会立即解除阻塞。同时ch2 可能已经触发,select 将在两个事件触发时解除阻塞。然后由实现按实现定义的顺序调用这两个案例处理程序。
【解决方案2】:

写入 ch1 不会立即导致选择操作继续。 goroutine 可以安排在通道写入之后或关闭操作之后,这样两种情况都有机会运行。如果 ch1 没有缓冲,那么写入操作只会在通道读取之后发生,因此程序将始终打印 ch1。

【讨论】:

  • 如果我确保 goroutine 已经在运行 before 我执行通道写入/关闭怎么办?这会有所作为吗?例如,通过使用在 goroutine 启动后完成的等待组?
  • 不会有什么不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-28
  • 2021-10-09
  • 2017-06-12
  • 2015-05-08
  • 2017-09-16
  • 2011-12-26
  • 2019-07-04
相关资源
最近更新 更多