Golang Channel详解
channel
的本质是一个并发安全的FIFO
队列
Go
中的channel
是实现CSP
并发模型的关键基础
CSP是 是 Communicating Sequential Processes(通信顺序进程)的简称。在 CSP 中,多了一个角色 Channel,Worker 之间不直接通信,而是通过 Channle 进行通信。
Channel 是过程的中间媒介,Worker1 想要跟 Worker2 发信息时,直接把信息放到 Channel 里(在程序中其实就是一块内存),然后 Worker2 在方便的时候到 Channel 里获取。
Channel 类似 Unix 中的 Pipe
初始化
c := make(chan int, 100)
方向
注:int
、float
、自定义类型等
定义时,也可以定义为单向chan
,但无实际使用意义
chan <T> // 默认为双向,可以接收和发送类型为 T 的数据,定义或函数入参时使用
chan<- <T> // 单向,只可以用来发送 <T> 类型的数据,在函数参数中使用, 这样可以限定chan使用
<-chan <T> // 单向,只可以用来接收 <T> 类型的数据,在函数参数中使用, 这样可以限定chan使用
<-
总是优先和最左边
的类型结合。(The <- operator associates with the leftmost chan possible)
chan<- chan int // 等价 chan<- (chan int)
chan<- <-chan int // 等价 chan<- (<-chan int)
<-chan <-chan int // 等价 <-chan (<-chan int)
chan (<-chan int)
同步异步
无缓存
同步channel
,发送者和接受者都会阻塞等待数据
c := make(chan int, 100)
c := make(chan int)
有缓存
对于发送者,当缓存未满时为异步不阻塞channel
,缓存满时为同步channel
对于接收者,当缓存不为空时为异步不阻塞channel
,为空时时为同步channel
其他操作
https://colobu.com/2016/04/14/Golang-Channels/
for …… range
func main() {
go func() {
time.Sleep(1 * time.Hour)
}()
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)
}()
for i := range c { //如果c的缓冲区为空,这个协程会一直阻塞,除非被channel被close
fmt.Println(i)
}
fmt.Println("Finished")
}
range c
产生的迭代值为Channel中发送的值,它会一直迭代直到channel被关闭。上面的例子中如果把close(c)
注释掉,程序会一直阻塞在for …… range
那一行
带缓冲的channel
,被关闭后也可以读出缓冲区的值,关闭后再对channel进行操作,行为如上面的表格所述。
select
select
语句选择一组可能的send操作和receive操作去处理。它类似switch
,但是每条case
只是用来处理channel
操作。
它的case
可以是send
语句,也可以是receive
语句,亦或者default
。
receive
语句可以将值赋值给一个或者两个变量。
最多允许有一个default case
,它可以放在case列表的任何位置,尽管我们大部分会将它放在最后
如果有同时多个case去处理,比如同时有多个channel可以接收数据,那么Go会伪随机的选择一个case处理(pseudo-random)。如果没有case需要处理,则会选择default
去处理,如果default case
存在的情况下。如果没有default case
,则select
语句会阻塞,直到某个case需要处理。
func chanFunc(c, quit chan int) {
for {
select {
case r, ok := <-c: // 建议使用双返回值,来确定channel是否被关闭
if ok {
fmt.Println("Goroutine Recv", r)
} else {
fmt.Println("Chan Closed")
}
case <-quit:
fmt.Println("Quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
fmt.Println("main Send", 2)
c <- 2
time.Sleep(time.Second)
close(c)
fmt.Println("Close c")
quit <- 0
}()
chanFunc(c, quit)
}
// main Send 2
// Goroutine Recv 2
// Close c
// Quit
与Timer
结合,实现timeout
select
有很重要的一个应用就是超时处理。 因为上面我们提到,如果没有case需要处理,select语句就会一直阻塞着。这时候我们可能就需要一个超时操作,用来处理超时的情况。
func main() {
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
// timeout 1
该demo的功能是,res
预期在1s钟时间内收到一条channel消息,否则超时
其他
channel是值传递?
行为总结
操作 | nil channel | closed channel | not-closed non-nil channel |
---|---|---|---|
close | panic | panic | 成功 close |
写 ch <- | 一直阻塞 | panic | 阻塞或成功写入数据 |
读 <- ch | 一直阻塞 | 读取对应类型零值 | 阻塞或成功读取数据 |
实现详解
https://changkun.de/golang/zh-cn/part2runtime/ch09lang/chan/
channel
的阻塞与唤醒 gopark
ready
参考资料
https://www.jianshu.com/p/554e210bdca4
http://litang.me/post/golang-channel/
http://legendtkl.com/2017/07/30/understanding-golang-channel/
https://colobu.com/2016/04/14/Golang-Channels/#Buffered_Channels
本文由 LeonardWang 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jul 14,2020