CSP(communicating sequential processes,通信顺序进程)用于描述并发系统中交互模式的形式化语言,其通过通道传递消息。
目录
通道写入数据
对于无缓存通道,能向通道写入数据的前提是有另外一个协程在读取通信,否则,当前协程会陷入休眠状态,直到能向通道写入数据。
无缓存通道的读和写应该位于不同的协程中,否则将陷入死循环。
通道读取数据
如果不能直接读取通道数据,则协程陷入阻塞,直到有协程写入通道为止。
读取通道有两个返回值。
data,ok := <-c
第一个表示读到的数据,第二个为布尔类型,返回false表示当前通道已关闭。若通道已关闭,则data为零值。注意:和读取已关闭通道,不同的是不能向已关闭的通道写入数据。
当通道没有被引用时将被Go语言的垃圾自动回收器收回,关闭通道会触发所有读取通道的操作被唤醒。这个特效很重要,例如用于当一个协程退出后,其创建的一系列子协程能快速退出的场景。
通道作为参数和返回值
通道作为一等公民,可以作为参数和返回值。由于通道在GO中是引用类型而不是值类型,因此作为参数或返回值传递到其他协程中的通道,实际上是用一个通道。
单项通道
为了表达语义防止通道滥用,GO语言提供当方向通道类型。chan<-float表示只能写入而不能读取浮点类型。<-chan string表示只能读取而不能写入字符串.
例如:
func worker(id int, c<-int) //表示只能读取通道消息。
main(){
var c = make(chan,int)
worker(0,c)
}
这里是因为具有读写功能的通道可以隐式转化为单通道类型。
反之,单通道类型的通道不能转为普通类型。
select随机选择机制
select中case是随机选取的,当select有两个通道同时准备好时,会随机选择执行不同case。
select堵塞与控制
若select中没有任何通道准备好时,那么select所在的协程会永远陷入等待,直到有一个case准备好为止。
select {
case <-c:
fmt.Println("01")
case <-b:
fmt.Println("02")
}
为了防止这种情况,可以加入defalut
select {
case <-c:
fmt.Println("01")
case <-b:
fmt.Println("02")
default:
fmt.Println("03")
}
或者使用定时器或者超时器配套
select {
case <-c:
fmt.Println("01")
case <-b:
fmt.Println("02")
case <-time.After(200*time.Millisecond):
fmt.Println("timeout")
}
这样当selece阻塞时,会等到超时后会向当前通道发送消息,然后select退出。
实际上,更常见的是我们不让select退出,于是加上for来循环这样就一直:阻塞等待消息->接受消息->执行->阻塞等待消息
select 与nil
当一个为nil的通道,不论读取还是写入都会陷入阻塞,当select语句的case对nil通道操作时,case分支将永远得不到执行。
总结
通道的实现底层是利用加锁实现的环形队列,在读出或者写入时如果不能直接操作则会被放入等待队列中陷入休眠状态。借助GO调度器,通道不会堵塞程序执行,并且协程在需要的时候能被快速唤醒。
select底层会锁住所有的通道,并采取随机的方式保证公平地遍历所有的通道。