golang channel 小问题请教

代码1

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    ch <- 1
    fmt.Println(<-ch) // 1

}

代码2

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    ch <- 1
    fmt.Println(<-ch) // 1

}

问题:
为什么代码1会报死锁的错误,而代码2不会报错?

阅读 3.8k
3 个回答

因为代码1的channel没有buffer,而代码2的channel设置了buffer为1。
没有buffer的channel只能通过另一个goroutine去读,否则就阻塞了。

func main() {
    messages := make(chan string)
    go func() { messages <- "ping" }()
    msg := <-messages
    fmt.Println(msg)
}

无缓冲的channel, 发送方会一直阻塞到接收方取数据;
所以在ch <- 1 就阻塞住了。

上面两位说的很清楚了,补充一下Happens Before原则:

对于一个单goroutine来说,读和写操作一定是按照他们语句的顺序被执行。编译器会重排序这些读写语句,这在单goroutine中并不会改变程序的行为。因为这种重排序,一个goroutine观察到的执行顺序可能和其他的goroutine是不一样的,如果一个goroutine执行了 a = 1 b = 2,其他的goroutine可能看到的是b先被复制,然后才是a被赋值。

为了指明读写操作所包含的,我们需要定义Happens Before原则,这是Go程序在内存中操作的准则:

  • 如果事件1先于事件2发生,我们就说事件2后于事件1发生。

  • 如果事件1并没有先于事件2发生,也没有后于事件2发生,那就说事件1和事件2是同时发生的。

在单goroutine中,这个顺序就是程序中表达式的顺序。
如果一个读操作r要想观察到写操作w对于变量v的写操作,就要满足一下的条件:

  • 读操作r不能先于写操作w之前发生。

  • 有其他对于变量v的写操作在写操作w之后并在读操作r之前发生。

为了保证这个读操作r观察到的是写操作w对于变量v的操作结果,为了保证写操作w是读操作r唯一允许的写操作,一定要满足这么两个条件:

  • w先于r之前发生。

  • 任何对于变量v的操作都要在w之前或者是r之后发生。

对于单goroutine来说,由于并没有并发,因此这两条定义是等价的:一个读操作r只能观察到离他最近的写操作w对于变量v的操作结果。当在多个goroutine中共享变量的时候,必须要使用同步事件来建立happens-before的条件以确保读操作r能够观察到它所期望的写操作的结果。

对于变量v的初始化为该类型的0值的行为在内存模型中被认为是一个写操作。

Reads and writes of values larger than a single machine word behave as multiple machine-word-sized operations in an unspecified order.

来自于The Go Memory Model 翻译的一般,见谅

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
1 篇内容引用
推荐问题
宣传栏