1.必须双方碰面才会携手往下走。
——无缓存通道的情形下,接收方(<-c1)和发送方(c1 <- true )会互相等待。
- 也即:如果接收方走到 <-c1 而发送方没有走到c1 <- true,则接收方协程会阻塞,等待发送方执行完c1 <- true 双方协程才会共同继续。反之亦然。
- 此时发送方和接收方的代码可以互换,不影响逻辑的进行。(但仍然建议由主协程或母协程来作为接收方)
2.塞满时发送方会等他腾出空位再往里塞。
——有缓存通道的情形下,如果缓存已满,会阻塞发送方的协程。
- 直到有接收方取出信息(也即执行 <-c1 ),为后续信息腾出空位。
3.拿空后接收方会等他有数据了再继续拿。
——有缓存通道的情形下,如果缓存为空,会阻塞接收方的协程。
- 直到有发送方存入信息(也即执行c1 <- true)。
相关代码如下
var c1 = make(chan bool)
func func1() {
//...
c1 <- true
//...
}
func main() {
go func1()
//...
<-c1
//...
select {} //保证协程结束
}
扩展应用:用缓存通道控制并发数目
- 本质是上文中 2. 的机制的运用
- 在大量并发的子协程的入口处使用c1 <- true,利用上文中2. 的机制阻塞新协程的开启;并在执行完毕后执行 <-c1,腾出缓存中的空间,此时则会放行某一个阻塞中的c1 <- true完成执行,进入后续步骤。如此宏观上的表现即为通道c1中定义的缓存数量等于所允许的子协程并发量的上限。
示例代码:
package main
import (
"time"
)
var limit = make(chan int, 3)
func main() {
for i := 0; i < 20; i++ {
limit <- 1 //缓存有空间,或接收方有就绪的,才会继续往下走
println("循环次数--", i)
go func(i int) { //同时也是协程持有的是引用的示例
//println("func-", i, "阻塞中")
//limit <- 1 //阻塞的代码也可以写在子协程生成后
println("执行中--", i)
time.Sleep(2 * time.Second)
println(" ", i, "--执行完成")
<-limit
//也即,从传入到传出的中间这段,同时只能存在3个运行中,第四个的limit <- 1会等第一批的三个中有一个走到<-limit才能继续(也即释放了一个chan的缓存|缓存有了空间,可以接收新的传入)
}(i)
}
select {}
}
观察结果可知,当缓存通道被占满后,每有一个“执行完成”,“循环次数”才会往下走一次。