go语言 channel通道 | 青训营笔记

143 阅读1分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第 1 篇笔记

简介

在go语言中,通道往往用来在goroutine间通信。通道可以让一个goroutine发送数据到另一个goroutine中去,每个通道能发送的数据类型是固定的,这叫做通道的元素类型,例如一个有类型为int 的通道我们写做:chan int

在程序中使用内置的 make 函数来创建一个通道:

ch := make(chan int)  //类型为int,传递int类型的值

通道主要有两种操作:向一个通道中发送值从一个通道中接收值,都使用<-操作符书写。发送语句中,目标通道在<-的左边,需要发送的值在<-右边。接收语句中,通道在<-的右边,其取出的值有没有被接收都是合法的

var x int =1
ch <- x    //发送语句

x = <- ch  //取出通道中的值并赋给x
<-ch     //只取值也是合法的

通道还有第三个操作:关闭一个通道在关闭后如果仍然向里面发送数据将导致程序panic宕机,在一个已经关闭的通道进行接收操作时,会获取通道中发送过的值,直到通道为空;这时候的任何接收操作都会立刻完成而不会阻塞(后面会说无缓冲通道和有缓存通道的阻塞性质)。如果通道为空则获取通道对应类型的零值

使用内置的close函数来关闭通道:

close(ch)

使用make函数我们可以创建两种类型的通道:无缓冲通道和缓冲通道

无缓冲通道

向一个无缓冲通道发送值时,会阻塞当前的goroutine,直到另一个goroutine在对应的通道上执行接收操作,值传送完成,两个goroutine才可以继续执行。同理如果接收操作先执行,接收方goroutine将阻塞,直到另一个goroutine在同一个通道上发送值。

使用无缓冲通道通信将导致发送和接收的goroutine同步化。因此无缓冲通道也被称为同步通道。当一个goroutine向一个无缓冲通道发送数据时,这个值被接收后发送方goroutine才会被唤醒。

缓冲通道

缓冲通道有一个元素队列,队列的最大长度在创建的时候通过make的容量参数来设置。下面的语句创建一个可以容纳三个整数的缓冲通道:

ch = make(chan int, 3)

缓冲通道上的发送操作将在队列尾部插入一个元素,接收操作会从队列头部取出一个元素。如果通道满了,缓冲通道会和无缓冲通道类似:向通道发送操作数据会阻塞当前goroutine直到另一个goroutine进行接收操作取出元素,腾出缓冲区。反过来,如果通道是空的,执行接收操作的goroutine会阻塞,直到另一个goroutine向这个通道发送数据。

当前可以无阻塞的向这个通道发送三个值:

ch <- 1
ch <- 2
ch <- 3

此时通道是满的。如果再向通道中发送第四个值将会导致阻塞。如果通道既不空也不满,这时接收操作和发送操作都不会阻塞。通过这种方式,通道的缓冲区将发送和接收goroutine解耦。

类似的想知道通道缓冲区的容量或者当前通道元素的个数可以用内置函数cap和len:

fmt.Println(cap(ch))  // "3"  获取缓冲区容量
fmt.Println(len(ch))  // "3"  获取当前通道内元素个数

单向通道类型

go的类型系统提供了单向通道类型,仅仅只能发送或接收数据。比如类型chan <- int 是一个只能发送的通道,允许发送但不允许接收。反之<-chan int 是一个只能接收int类型的通道,允许接收但是不允许发送。违背这个原则将在编译时被检查出来。

ch1 := make(<-chan int, 3)    //创建缓冲区容量为3的只接收通道
ch2 := make(chan<- int, 3)    //创建缓冲区容量为3的只发送通道
ch3 := make(chan<- int)       //创建只发送无缓冲通道
ch4 := make(<-chan int)       //创建只接收无缓冲通道

注意在任何赋值操作中将双向通道转换为单向通道都是允许的,但反之不行。

select多路复用

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。通用形式如下:

select {
case <-ch1:
   // ...
case <-ch2:
   // ...
case ch3 <- x:
   // ...
default:
   // ...
}

每一个情况指定一次通信和关联的代码块操作。select会一直等待,直到一次通信来告知一些情况可以执行。然后它进行这次通信,执行此情况所对应的语句;其他通信将不会发生。如果没有对应情况,select{}会一直等待。

如果多个情况同时满足,select会随机选择一个,这样保证每一个通道有相同的机会被选中。

最后引用go语言的一句名言:

应该通过通信来共享内存,而不是通过共享内存来通信