【问题标题】:How to understand the channel communication rules in golang memory model?如何理解 golang 内存模型中的通道通信规则?
【发布时间】:2018-03-31 01:34:00
【问题描述】:

在学习golang的途中,我在尝试理解内存模型规范中描述的通道通信时有点困惑,如下所示:

  1. 通道上的发送发生在来自该通道的相应接收完成之前。
  2. 通道关闭发生在接收返回零值之前,因为通道已关闭。
  3. 在该通道上的发送完成之前发生来自无缓冲通道的接收。
  4. 容量为 C 的通道上的第 k 次接收发生在该通道的第 k+C 次发送完成之前。

前两条规则清晰易懂,而我对第三条规则感到困惑,这似乎与其他规则相反......我错过了无缓冲通道的特别之处吗?或者我是正确的吗?如果我按照规范中的示例如下所示:

var c = make(chan int)
var a string

func f() {
    a = "hello, world"
    <-c    // A
}
func main() {
    go f()
    c <- 0 // B
    print(a)
}

对于一个无缓冲的通道,发送操作(B)被阻塞,直到接收者准备好接收值(A)? (如:B 开始,直到 A 执行才返回)准确吗?

我在Effective Go spec 中找到了以下陈述,但与我的理解仍然存在差异......那么有人可以简单直接地解释一下吗?

接收器总是阻塞,直到有数据接收。 如果频道 是无缓冲的,发送方阻塞,直到接收方收到 如果通道有缓冲区,发送方只会阻塞,直到 值已被复制到缓冲区;如果缓冲区已满,这意味着 等到某个接收者检索到一个值。

【问题讨论】:

  • “发生在之前” HB 有一个非常特殊的含义,在线性时间上与传统的“之前”只是松散相关。您可以拥有两个事件xy,同时保持两个关系:x HB yy HB x。规则 1 和 3 一起粗略地说:“在无缓冲通道上发送和接收正确同步并按预期工作(无论 CPU 重新组织指令的方式有多花哨,内存缓存缓存的速度有多快等等。”
  • @Volker 嗯...仍然对为什么 x HB yy HB x 都成立感到困惑,哈哈。可能,我需要将事件e 拆分为不同的原子阶段,例如开始、处理和结束,以便正确理解这个happens-before 术语...谢谢!
  • HB 并不意味着某件事实际上发生在比其他事情更早的时间,它只是一个方便的助记符。 HB 图由部分 HB 顺序建立,确定哪些内存操作在哪里可见。这是一个模型而不是现实。 HB 建立的是同步保证。它没有说明实际发生的事情。
  • 显然happens-before 一词具有误导性,请参阅此示例play.golang.org/p/1NJrpo-hnjQ... 我发现自己对执行代码如何根据使用通道作为信号进行流动有点困惑。

标签: go channel memory-model happens-before


【解决方案1】:

您突出显示的句子是您正在寻找的简单解释。

如果通道没有缓冲,发送方会阻塞,直到接收方收到该值。

这是第3点的另一种说法:

来自无缓冲通道的接收发生在该通道上的发送完成之前。

当您在无缓冲通道上发送时,发送方会阻塞,直到接收方获取该值。这意味着接收发生在发送完成之前

缓冲通道不同,因为值有去处。如果您仍然感到困惑,一个示例可能会有所帮助:

假设我想在你家留下一个包裹:

  • 如果频道被缓冲,您可以在某个地方让我离开包裹 - 也许是邮箱。这意味着我可以在您收到包裹(在您查看邮箱时)之前完成我的任务(通过频道发送)。
  • 如果频道没有缓冲,我必须在你的前门等,直到你来把包裹从我身上拿走。在我完成交付给您的任务之前,您会收到包裹。

对于无缓冲通道,发送操作(B) 会被阻塞,直到接收方准备好接收值(A)? (比如:B 启动,直到 A 执行才返回)准确吗?

是的。这是正确的。

【讨论】:

  • 很好的例子,太有帮助了!谢谢!
  • 所以“发生在之前”与“之前执行”的意思完全不同,很好解释顺便说一句..虽然我不明白什么是重要的事情要记住。我的意思是,什么如果发件人发生在之前或收件人发生在之前,这是我应该注意的重要事情,只要没有死锁..我想看代码示例,解释这两种情况(蒂莫西你的帖子很棒,但仍然是如何它会影响我们的代码设计决策)。谢谢大家
  • 很好的例子,通俗易懂!谢谢
【解决方案2】:

对于无缓冲通道,发送操作(B) 会被阻塞,直到接收方准备好接收值(A)? (比如:B 启动,直到 A 执行才返回)准确吗?

是的,这是准确的。就像文档说的那样,如果一个通道是无缓冲的,那么发送方将阻塞,直到收到该值。如果从未收到该值,您将陷入死锁并且程序将超时,例如 this 示例:

var c = make(chan int)

func main() {
    c <- 0
    println("Will not print")
}

因此,一个无缓冲的通道将在发送操作中阻塞,直到接收器准备好接收该值,即使这需要一段时间。然而,对于缓冲通道,阻塞将发生在接收操作中。 This 示例显示了无缓冲通道如何等待接收值,而缓冲通道则没有:

package main

import "time"

var c chan int
var a string

func f() {
    time.Sleep(3)
    a = "hello, world"
    <-c // A
}
func test() {
    a = "goodbye"
    go f()
    c <- 0 // B
    println(a)
}

func main() {
    // Unbuffered
    c = make(chan int)
    test()

    // Buffered
    c = make(chan int, 1)
    test()
}

输出:

hello, world
goodbye

【讨论】:

  • 我认为你有 AB 错误的方式。 OP 的声明是正确的 - B 确实阻塞,直到 A 发生(如果 A 从未发生,则永远)。
  • @TimothyJones 是的,你是对的。我提供的例子反映了这一点,但我的陈述没有。
  • @JackGore 缓冲通道与无缓冲通道的好例子,现在很清楚了。还有有趣的超时示例。谢谢!
  • @JackGore 我认为最好将其称为deadlock 示例而不是超时:程序由于死锁而退出。我还找到了this blog about timeout,有趣的是它提到了通过选择在无缓冲通道上进行非阻塞发送。
【解决方案3】:

在我阅读下一条规则后,我开始明白为什么必须这样:

容量为 C 的通道上的第 k 次接收发生在该通道的第 k+C 次发送完成之前。

是一样的,你需要能够同步例程,所以接收者等待发送者。

【讨论】:

    猜你喜欢
    • 2017-11-09
    • 2019-05-10
    • 2021-04-29
    • 2021-01-03
    • 2016-11-19
    • 2017-10-27
    • 2016-11-07
    • 1970-01-01
    • 2022-01-06
    相关资源
    最近更新 更多