golang - channel

2022-04-15  本文已影响0人  husky_1

1. 原理

hchan

通过var声明或者make函数创建的channel变量是一个存储在函数栈帧上的指针,占用8个字节,指向堆上的hchan结构体
源码包中src/runtime/chan.go定义了hchan的数据结构如下:


hchan
type hchan struct {
    qcount   uint           // total data in the queue   循环数组中的元素数量
    dataqsiz uint           // size of the circular queue  循环数组的长度
        //channel分为无缓冲和有缓冲两种。
       // 对于有缓冲的channel存储数据,使用了 ring buffer(环形缓冲区) 来缓存写入的数据,本质是循环数组
       // 为啥是循环数组?普通数组不行吗,普通数组容量固定更适合指定的空间,弹出元素时,普通数组需要全部都前移
        // 当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置
    buf      unsafe.Pointer // points to an array of dataqsiz elements  指向底层循环数组的指针(环形缓冲区)
    elemsize uint16     //元素的大小
    closed   uint32       //channel是否关闭的标志
    elemtype *_type // element type  channel中的元素类型
    sendx    uint   // send index   // 下一次写下标的位置
    recvx    uint   // receive index    // 下一次读下标的位置
    recvq    waitq  // list of recv waiters  // 读等待队列
    sendq    waitq  // list of send waiters  // 写等待队列

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex    //互斥锁,保证读写channel时不存在并发竞争问题
}

hchan结构体的主要组成部分有四个:
用来保存goroutine之间传递数据的循环数组:buf
用来记录此循环数组当前发送或接收数据的下标值:sendx和recvx
用于保存向该chan发送和从该chan接收数据被阻塞的goroutine队列: sendq 和 recvq
保证channel写入和读取数据时线程安全的锁:lock

环形数组

环形数组作为channel 的缓冲区 数组的长度就是定义channnel 时channel 的缓冲大小


等待队列 waitq

在hchan 中包括了读/写 等待队列, waitq是一个双向队列,包括了一个头结点和尾节点。 每个节点是一个sudog结构体变量

type waitq struct {
    first *sudog
    last  *sudog
}


type sudog struct {
    // The following fields are protected by the hchan.lock of the
    // channel this sudog is blocking on. shrinkstack depends on
    // this for sudogs involved in channel ops.

    g *g

    next *sudog
    prev *sudog
    elem unsafe.Pointer // data element (may point to stack)

    // The following fields are never accessed concurrently.
    // For channels, waitlink is only accessed by g.
    // For semaphores, all fields (including the ones above)
    // are only accessed when holding a semaRoot lock.

    acquiretime int64
    releasetime int64
    ticket      uint32

    // isSelect indicates g is participating in a select, so
    // g.selectDone must be CAS'd to win the wake-up race.
    isSelect bool

    // success indicates whether communication over channel c
    // succeeded. It is true if the goroutine was awoken because a
    // value was delivered over channel c, and false if awoken
    // because c was closed.
    success bool

    parent   *sudog // semaRoot binary tree
    waitlink *sudog // g.waiting list or semaRoot
    waittail *sudog // semaRoot
    c        *hchan // channel
}
操作

2. 特点

channel有2种类型:无缓冲、有缓冲, 在创建时make(chan type cap) 通过cap 设定缓冲大小
channel有3种模式:写操作模式(单向通道)、读操作模式(单向通道)、读写操作模式(双向通道)

写操作模式 读操作模式 读写操作模式
创建 make(chan<- int) make(<-chan int) make(chan int)

channel有3种状态:未初始化、正常、关闭

未初始化 关闭 正常
关闭 panic panic 正常关闭
发送 永远阻塞导致死锁 panic 阻塞或者成功发送
接收 永远阻塞导致死锁 缓冲区为空则为零值, 否则可以继续读 阻塞或者成功接收

如下几种状态会引发panic

1.关闭未初始化的channel 和已经关闭的channel

  1. 向已经关闭的channel 中发送数据
3. 线程安全

channel 是线程安全的,channel的底层实现中,hchan结构体中采用Mutex锁来保证数据读写安全。在对循环数组buf中的数据进行入队和出队操作时,必须先获取互斥锁,才能操作channel数据

上一篇 下一篇

猜你喜欢

热点阅读