send被执行前(proceed)通讯(communication)一直被阻塞着。如前所言,无缓存的channel只有在receiver准备好后send才被执行。如果有缓存,并且缓存未满,则send会被执行。
往一个已经被close的channel中继续发送数据会导致run-time panic。
往nil channel中发送数据会一致被阻塞着。
- receive 操作符
<-ch
用来从channel ch中接收数据,这个表达式会一直被block,直到有数据可以接收。
从一个nil channel中接收数据会一直被block。
从一个被close的channel中接收数据不会被阻塞,而是立即返回,接收完已发送的数据后会返回元素类型的零值(zero value)。
如前所述,你可以使用一个额外的返回参数来检查channel是否关闭。
1 2 3 | x, ok := <-ch x, ok = <-ch var x, ok = <-ch |
如果OK 是false,表明接收的x是产生的零值,这个channel被关闭了或者为空。
Happens Before的作用
Happens Before主要是用来保证内存操作的可见性。如果要保证E1的内存写操作能够被E2读到,那么需要满足:
- E1 Happens Before E2;
- 其他所有针对此内存的写操作,要么Happens Before E1,要么Happens After E2。也就是说不能存在其他的一个写操作E3,这个E3 Happens Concurrently E1/E2。
为什么需要定义Happens Before关系来保证内存操作的可见性呢?原因是没有限制的情况下,编译器和CPU使用的各种优化,会对此造成影响,具体的来说就是操作重排序和CPU CacheLine缓存同步:
- 操作重排序。现代CPU通常是流水线架构,且具有多个核心,这样多条指令就可以同时执行。然而有时候出现一条指令需要等待之前指令的结果,或是其他造成指令执行需要延迟的情况。这个时候可以先执行下一条已经准备好的指令,以尽可能高效的利用CPU。操作重排序可以在两个阶段出现:
- 编译器指令重排序
- CPU乱序执行
- CPU 多核心间独立Cache Line的同步问题。多核CPU通常有自己的一级缓存和二级缓存,访问缓存的数据很快。但是如果缓存没有同步到主存和其他核心的缓存,其他核心读取缓存就会读到过期的数据。
声明但不make初始化的chan是nil chan。读写nil chan会阻塞,关闭nil chan会panic。
写closed chan或关闭 closed chan会导致panic。读closed chan永远不会阻塞,会返回一个通道数据类型的零值。
channel happens-before:
- 对带缓冲chan的写操作 happens-before相应chan的读操作
- 关闭chan happens-before 从该chan读最后的返回值0
- 不带缓冲的chan的读操作 happens-before相应chan的写操作
var c = make(chan int, 10)
var a string
func f() {
a = "hello, world" //(1)
c <- 0 // (2)
}
func main() {
go f()
<- c //(3)
print(a) //(4)
}
(1) happens-before(2) (3) happens-before(4),再根据规则可知(2) happens(3)。因此(1)happens-before(4),这段代码没有问题,肯定会输出hello world。
var c = make(chan int)
var a string
func f() {
a = "hello, world" //(1)
<-c // (2)
}
func main() {
go f()
c <- 0 //(3)
print(a) //(4)
}
同样根据规则三可知(2)happens-before(3) 最终可以保证(1) happens-before(4)。若c改成待缓冲的chan,则结果将不再有任何同步保证使得(2) happens-before(3)。
参考:
https://zhuanlan.zhihu.com/p/29108170
https://ninokop.github.io/2017/11/07/Go-Channel%E7%9A%84%E5%AE%9E%E7%8E%B0/