什么是channel
从字面上看,channel
的意思大概就是管道的意思。channel
是一种go协程用以接收或发送消息的安全的消息队列,channel
就像两个go协程之间的导管,来实现各种资源的同步。可以用下图示意:
channel
的用法很简单:
func main() { ch := make(chan int, 1) // 创建一个类型为int,缓冲区大小为1的channel ch <- 2 // 将2发送到ch n, ok := <- ch // n接收从ch发出的值 if ok { fmt.Println(n) // 2 } close(ch) // 关闭channel }
使用channel
时有几个注意点:
- 向一个
nil
channel
发送消息,会一直阻塞; - 向一个已经关闭的
channel
发送消息,会引发运行时恐慌(panic)
; channel
关闭后不可以继续向channel
发送消息,但可以继续从channel
接收消息;- 当
channel
关闭并且缓冲区为空时,继续从从channel
接收消息会得到一个对应类型的零值。
Unbuffered channels与Buffered channels
Unbuffered channels
是指缓冲区大小为0的channel
,这种channel
的接收者会阻塞直至接收到消息,发送者会阻塞直至接收者接收到消息,这种机制可以用于两个goroutine
进行状态同步;Buffered channels
拥有缓冲区,发送者在将消息发送到缓冲区之前是阻塞的,当缓冲区已满时,发送者会阻塞;当缓冲区为空时,接收者会阻塞。
引用The Nature Of Channels In Go中的两张图来说明Unbuffered channels
与Buffered channels
, 非常形象,读者可自行体会一下:
Unbuffered channels
:
Unbuffered channels
Buffered channels
:
Buffered channels
channel的遍历
for range
channel
支持 for range
的方式进行遍历:
package main import "fmt" func main() { ci := make(chan int, 5) for i := 1; i <= 5; i++ { ci <- i } close(ci) for i := range ci { fmt.Println(i) } }
值得注意的是,在遍历时,如果channel
没有关闭,那么会一直等待下去,出现 deadlock
的错误;如果在遍历时channel
已经关闭,那么在遍历完数据后自动退出遍历。也就是说,for range
的遍历方式时阻塞型的遍历方式。
for select
select
可以处理非阻塞式消息发送、接收及多路选择。
package main import "fmt" func main() { ci := make(chan int, 2) for i := 1; i <= 2; i++ { ci <- i } close(ci) cs := make(chan string, 2) cs <- "hi" cs <- "golang" close(cs) ciClosed, csClosed := false, false for { if ciClosed && csClosed { return } select { case i, ok := <-ci: if ok { fmt.Println(i) } else { ciClosed = true fmt.Println("ci closed") } case s, ok := <-cs: if ok { fmt.Println(s) } else { csClosed = true fmt.Println("cs closed") } default: fmt.Println("waiting...") } } }
select
中有case
代码块,用于channel
发送或接收消息,任意一个case
代码块准备好时,执行其对应内容;多个case
代码块准备好时,随机选择一个case
代码块并执行;所有case
代码块都没有准备好,则等待;还可以有一个default
代码块,所有case
代码块都没有准备好时执行default
代码块。