go channel 底层实现原理

2023-07-23  本文已影响0人  Newzer

首先看一下它的结构体

type hchan struct {
    qcount   uint           // total data in the queue,队列长度
    dataqsiz uint           // size of the circular queue,队列容量
    buf      unsafe.Pointer // points to an array of dataqsiz elements,队列底层数组指针
    elemsize uint16 //元素类型大小
    closed   uint32 //是否关闭 
    elemtype *_type // element type,元素类型
    sendx    uint   // send index,发送索引
    recvx    uint   // receive index,接收索引
    recvq    waitq  // list of recv waiters,接收goroutine链表
    sendq    waitq  // list of send waiters,发送goroutine链表

    // 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 //锁
}

1.有lock字段就说明channe是线程安全的
2.recvq,sendq都是goroutine链表的封装,recvq表示这个channel的多个接收goroutine,sendq表示这个channel的多个发送goroutine,在发送数据或者关闭channel的时候会用到这两个字段
3.qcount,dataqsiz,buf,sendx,recvx 这几个字段都是和缓冲channel有关的,sendx,recvx会在发送数据和接收数据的时候会用到这两个字段

发送数据原理:
1.发送数据
2.轮训检查recvq链表中是否有接收的goroutine,如果有,直接就把这个发送的值拷贝到接收goroutine的栈里直接返回,如果没有进行下一步
3.如果是一个带缓冲区的channel,检查容量是否已满,不满则根据sendx插入到缓冲数组中,直接返回,已满则该协程阻塞

接收数据原理:
1.检查channel是否已经关闭,关闭了就直接跳到第3步,没关闭就第2步
2.轮训检查sendq链表中是否有发送的goroutine,如果有,直接就把这个发送的值拷贝到接收goroutine的栈里直接返回,如果没有进行下一步
2.如果是一个带缓冲区的channel,检查循环数组中是否有可以发送的元素,有则根据recvx将元素拷贝到该goroutine栈中,直接返回,没有可放出的元素则该协程阻塞

所以仍然可以从一个已经关闭的带有缓冲区的channel读取出数据
如果一个channel被多个goroutine监听,则数据会随机被某个goroutine消费

总的来说,channe 协程间通信的本质是拷贝,优先是recvq,sendq与当前协程之间直接的数据拷贝,若存在缓冲区,则还有缓冲区和协程之间的拷贝

关闭channel时发生了什么?
先将closed赋值为1
加一把大锁【遍历所有的recvq,sendq加入到一个全部协程池】解锁,唤醒全部的协程,所有sendq协程直接panic,所有recvq返回默认值

上一篇下一篇

猜你喜欢

热点阅读