Post

channel 原理

channel 原理

原理

channel 是 Go 中实现 goroutine 之间通信和同步 的核心机制。

底层数据结构是 hchan,主要由环形队列 + 锁 + 发送/接收队列 + 调度器组成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type hchan struct {
  qcount   uint      	// 队列中剩余元素数量
  dataqsiz uint     		// 循环队列的长度(channel的大小)
  buf      unsafe.Pointer // 长度为dataqsiz的底层数组指针,缓存型channel特有
  elemsize uint16		// 元素大小
  closed   uint32		// 是否关闭
  elemtype *_type 		// 接收、发送的的元素类型
  sendx    uint  		// 已发送元素在循环队列中的索引位置
  recvx    uint  		// 已接收元素在循环队列中的索引位置
  recvq    waitq  		// 接收者sudog等待队列(阻塞等待接收的goroutine)
  sendq    waitq  		// 发送者sudog等待对列(阻塞等待接收的goroutine)
  
  lock mutex				// 互斥锁
}
  • 环形队列: 当 chan 是有缓冲型,负责缓存数据。
  • 锁:并发控制
  • 发送/接收队列:保存发送/接收阻塞等待的 goroutine 。
  • 调度器:由 Go runtime 调度器完成

发送数据时,如果有等待的接收 goroutine,会直接将数据交给接收者;如果没有且缓冲区未满,则写入环形队列;如果缓冲区已满,则发送 goroutine 会进入 sendq 队列并被阻塞。

接收数据时,如果缓冲区有数据则直接读取;如果没有但有等待发送者,则直接接收数据;否则接收 goroutine 会进入 recvq 队列阻塞。

这里思考一个问题,那 goroutine1 和 goroutine2 又怎么互相知道自己的数据 ”到“ 了呢?

channel结构中的recvq、sendq保存着阻塞等待的goroutine,但goroutine1向环型队列中发送数据时,就会从recvp取出goroutine并唤醒。

channel OR mutex

  • 关注数据流动,考虑使用channel解决
  • 数据不流动,保护数据,使用mutex

关闭channel原则

The Channel Closing Principle:不要在接收端关闭channel,也不要关闭有多个并发发送者的channel

打破The Channel Closing Principle解决方案

  • panic与recover

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
      func SafeSend(ch chan T, value T) (closed bool) {
          defer func() {
              if recover() != nil {
                  // the return result can be altered 
                  // in a defer function call
                  closed = true
              }
          }()
              
          ch <- value // panic if ch is closed
          return false // <=> closed = false; return
      }
      
  • sync.Once关闭channel
  • sync.Mutex关闭channel

参考

  • https://mp.weixin.qq.com/s/ZXYpfLNGyej0df2zXqfnHQ
  • https://www.cnblogs.com/-wenli/p/12710361.html
  • https://segmentfault.com/a/1190000019172554
This post is licensed under CC BY 4.0 by the author.