Go - Channel

# Channel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
type hchan struct {
  qcount   uint                 // 队列中所有数据总数
  dataqsiz uint                 // 循环队列大小
  buf      unsafe.Pointer       // 指向循环队列的指针
  elemsize uint16               // 循环队列中元素的大小
  closed   uint32               // chan是否关闭的标识
  elemtype *_type               // 循环队列中元素的类型
  sendx    uint                 // 已发送元素在循环队列中的位置
  recvx    uint                 // 已接收元素在循环队列中的位置
  recvq    waitq                // 等待接收的goroutine的等待队列
  sendq    waitq                // 等待发送的goroutine的等待队列
  lock mutex                    // 控制chan并发访问的互斥锁
}

type waitq struct {
  first *sudog
  last *sudog
}

.

  1. Channel 本质上是由三个 FIFO(First In FirstOut,先进先出)队列组成的用于协程之间传输数据的协程安全的通道;FIFO 的设计是为了保障公平,让事情变得简单,原则是让等待时间最长的协程最有资格先从 channel 发送或接收数据;
  2. 三个 FIFO 队列依次是 buf 循环队列,sendq 待发送者队列,recvq 待接收者队列。buf 循环队列是大小固定的用来存放 channel 接收的数据的队列;sendq 待发送者队列,用来存放等待发送数据到 channel 的 goroutine 的双向链表,recvq 待接收者队列,用来存放等待从 channel 读取数据的 goroutine 的双向链表;sendq 和 recvq 可以认为不限大小;
  3. 跟函数调用传参本质都是传值一样,channel 传递数据的本质就是值拷贝,引用类型数据的传递也是地址拷贝;有从缓冲区 buf 地址拷贝数据到接收者 receiver 栈内存地址,也有从发送者 sender 栈内存地址拷贝数据到缓冲区 buf;
  4. Channel 里面参数的修改不是并发安全的,包括对三个队列及其他参数的访问,因此需要加锁,本质上,channel 就是一个有锁队列;
  5. Channel 的性能跟 sync.Mutex 差不多,没有谁比谁强。Go 官方之所以推荐使用 Channel 进行并发协程的数据交互,是因为 channel 的设计理念能让程序变得简单,在大型程序、高并发复杂的运行状况中也是如此。

Happens Before

  1. 修改由多个 goroutines 同时访问的数据的程序必须串行化这些访问。
  2. 为了实现串行访问, 需要使用 channel 操作或其他同步原语(如 sync 和 sync/atomic 包中的原语)来保护数据。
  3. go 语句创建一个 goroutine,一定发生在 goroutine 执行之前。
  4. 往一个 channel 中发送数据,一定发生在从这个 channel 读取这个数据完成之前。
  5. 一个 channel 的关闭,一定发生在从这个 channel 读取到零值数据(这里指因为 close 而返回的零值数据)之前。
  6. 从一个无缓冲 channel 的读取数据,一定发生在往这个 channel 发送数据完成之前。

# References

Licensed under CC BY-NC-SA 4.0
Get Things Done
Built with Hugo
Theme Stack designed by Jimmy