Channel
-
管道是Go在语言层面提供的协程间的通信方式。
-
管道有无缓冲管道和带缓冲管道
ch1 := make(chan string) //无缓冲 ch2 := make(chan string, 5) //有缓冲
-
数据读写
- 无缓冲:从管道中读写数据会被阻塞,直到有协程向管道中写或读数据。
- 有缓冲:有缓冲但缓冲为空的Chan读取数据时也会被阻塞,直到有协程写入数据;有缓冲但缓冲已满的Chan中写数据时也会被阻塞,直到有协程读取数据。
- 对于值为nil的Chan,无论读写都会阻塞,而且是永久阻塞。
-
阻塞的条件:
-
协程读取管道时,阻塞的条件有:
管道无缓冲区 管道缓冲区为空 管道的值为nil
-
协程写入管道时,阻塞的条件有:
管道无缓冲区 管道缓冲区已满 管道的值为nil
-
-
管道的数据结构
type hchan struct{ //环形队列 qcount uint //当前队列中剩余元素的个数 len(ch1) dataqsiz uint //环形队列长度,即cap(ch1) buf unsafe.Pointer //环形队列指针 elemsize uint16 //每个元素的大小 closed uint32 //标识关闭状态 elemtype *_type //元素类型 sendx uint //队列下标,元素写入时存放到队列中的位置 recvx uint //队列下标,下一个被读取的元素在队列中的位置 recvq waitq //等待读消息的协程队列 sendq waitq //等待写消息的协程队列 lock mutex //互斥锁,chan不允许并发读写 }
-
管道的读写操作
-
向管道中写数据
- 如果缓冲区有空余位置,将数据写入缓冲区中,结束发送进程
- 如果缓冲区中没有空余位置,将当前协程加入senq队列,进入睡眠并等待被唤醒。
注意:上面形容缓冲区时,用是否有空余位置来形容,是因为存在着无缓冲区的chan
在实现时,当接收队列recvq不为空,说明缓冲区中没有数据,但是有协程在等待读数据,此时会把数据直接传递给recvq队列中的第一个协程,而不必在写入缓冲区。
-
从管道中读数据
- 如果缓冲区中有数据,则从缓冲区中读数据,结束读取进程
- 如果缓冲区中无数据,则将当前协程加入recvq队列,进入睡眠并等待被写协程唤醒
在实现时,如果等待发送队列sendq不为空,且没有缓冲区,那么此时将直接从sendq队列的第一个协程获取数据。
-
关闭管道
关闭管道时会把recvq中的协程全部唤醒,这些协程获取数据都为对应类型的零值;同时会把sendq队列中的携程全部唤醒,但这些协程都会触发panic;
除此之外,还存在一些其他触发panic的操作
- 关闭值为nil的管道
- 关闭已经关闭的管道
- 向已经被关闭的管道发送数据
-