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