通道类型的值本身就是并发安全的,这也是 Go 语言自带的、唯一一个可以满足并发安全性的类型。
通道声明及初始化
通道相当于一个先进先出的队列。在声明一个通道类型变量的时候,我们首先要确定该通道类型的元素类型,决定了我们可以通过这个通道传递什么样的数据。
声明并初始化一个通道时需要使用内建函数 make 。 make 第二个参数可选,用于表示通道的容量。
ch := make(chan int, 2)
当容量为0时,该通道为 非缓冲通道 。大于0时为 缓冲通道 。
元素值的发送和接受都需要使用操作符 <- (也叫接送操作符)。用法如下:
-
ch <-将元素发送至通道 ch
-
<- ch也被叫做接收表达式,用于表达从该通道接收一个元素值。
package mainimport "fmt"func main() {ch := make(chan int, 3)ch <- 4//像通道ch 发送一个元素值2ch <- 3ch <- 1elem := <- ch//从通道中接收一个元素值fmt.Println(elem)//4fmt.Println(len(ch))//2}
引发阻塞的操作
缓冲通道
如果通道已满,那么所有的发送到该通道的操作都会被阻塞,直到通道中有元素被接收走。通道会优先通知最早因此等待的那个操作所在的goroutine,再次进行发送。
如果通道已空,那么对它的所有接收操作都会被阻塞,直到通道中有新元素出现。道会优先通知最早因此等待的那个操作所在的goroutine,再次进行接收。
非缓冲通道
无论是发送还是接收,一开始执行就会被阻塞,直到出现配对的操作也开始,才会继续传递。由此可见,非缓冲通道是采用同步的方式进行传递。
值为nil的通道
对值为nil的通道进行发送和接收都会永久阻塞状态。它们所属的goroutine中的代码都不再会执行。
由于通道类型是引用类型,所以它的零值就是 nil 。所以只声明了该类型的变量但是没有用make 初始化时,它的值就会是 nil
引发panic的操作
下边罗列一下会引发 panic 的情况:
-
通道关闭后,继续向它进行发送操作
-
试图关闭一个已经关闭的通道
千万不要让接收方关闭通道,应该让发送方做这件事
单向通道
只能发不能收或者只能收不能发的通道就是单向通道。例:
ch := make(chan<- int, 1)
上述通道表示只能发不能收。简称为 发送通道
通道常用操作
package mainimport "fmt"func main() {ch := getChan()for elem := range ch {fmt.Println(elem)}}func getChan() <-chan int {ch := make(chan int, 5)for i := 0; i < 5; i++ {ch <- i}close(ch)return ch}
下边例子为 select 与通道联合使用
package mainimport ("fmt""math/rand")func main() {channels := [3]chan int {make(chan int, 1),make(chan int, 1),make(chan int, 1),}randInt := rand.Intn(3)fmt.Println("value:", randInt)channels[randInt] <- randIntselect {case elem := <-channels[0]:fmt.Println("first channel, value:", elem)case elem := <-channels[1]:fmt.Println("second channel, value:", elem)case elem := <-channels[2]:fmt.Println("third channel, value:", elem)default:fmt.Println("error")}}
select 有默认分支时不会被阻塞。
package mainimport ("fmt""time")func main() {ch := make(chan int, 1)time.AfterFunc(time.Second, func() {close(ch)})select {case _, ok := <-ch://判断通道是否已关闭if !ok {fmt.Println("ch is closed")break}}}