如何理解golang内存模型中的通道通信规则?

时间:2017-10-19 04:05:08

标签: go channel memory-model happens-before

在学习golang的过程中,当我试图理解内存模型规范中描述的通道通信时,我有点困惑:

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

前两条规则清晰且易于理解,而我对第三条规则感到困惑,这与其他规则相反......我是否对无缓冲频道有任何特别的看法?或者我是否正确如果我按照规范中的示例使用以下内容:

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中找到了以下陈述,但我的理解仍然存在差异......那么有人可以用一种简单直接的方式解释这一点吗?

  

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

3 个答案:

答案 0 :(得分:7)

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

  

如果信道是无缓冲的,则发送方将阻塞,直到接收方收到该值。

这是说点3的另一种方式:

  

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

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

缓冲通道是不同的,因为值已经到了某个地方。如果你仍然感到困惑,一个例子可能会有所帮助:

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

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

对于无缓冲通道,发送操作(B)被阻止,直到接收器准备好接收值(A)? (比如:B开始,直到A执行才返回)是否准确?

是。这是正确的。

答案 1 :(得分:1)

  

对于无缓冲通道,发送操作(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

答案 2 :(得分:0)

对我来说,在我阅读下一条规则后,为什么必须如此:

<块引用>

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

有点相同,您需要能够同步例程,以便接收者等待发送者。