go channel

lucasgao · · 701 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

[toc]


Channel

编译器翻译

image-20200504174721504

关键数据结构

  1. hchan
  2. sudog

hchan

<img src="http://picgo.vipkk.work/20200504175832.png" alt="image-20200504175832482" style="zoom:50%;" />

sudog

<img src="http://picgo.vipkk.work/20200505204907.png" alt="image-20200505204907061" style="zoom:50%;" />

image-20200504175803583

其中buf + sendx + recvx组成ring buffer.

sendq 和 recvq是双向链表,用于储存等待的g。

sudog中的elem是chan用来传递的元素

源码

init

  1. 判读是否溢出,溢出则直接panic
  2. 如果要申请的内存为0,则把buf 置为racebuf
  3. 如果内存不为0,但buf中元素不是指针,则内存分配在stack上与chan连续
  4. 否则分配到堆上
  5. 实例化 数据类型和缓存大小

send

所有的返回都会unlock

  1. 如果chan为nil,则返回
  2. fasepath,快速失败(TODO)
  3. 获取锁
  4. 如果此时有receiver在等待,则直接发给他over,返回发送成功
    1. 需要将对应的receiver 执行goready
  5. 否则就判断此时buf是否已满,不满的话放入,更新ringbuffer指针
    1. 需要将对应的receiver 执行goready
  6. 如果buf已满,将g包装成sudog,然后park等待
  7. park恢复,释放内存,释放sudog

recv

  1. 如果chan为nil,则返回
  2. fasepath,快速失败(TODO)
  3. 获取锁
  4. 如果此时有sender在等待,则直接发给他over,返回发送成功
    1. 需要将对应的sender 执行goready
  5. 否则就判断此时缓存中是否有数据,有的话就执行内存copy,更新ringbuffer指针
    1. 需要将对应的sender 执行goready
  6. 如果缓存为空,则把g包装为sudog,然后park等待。
  7. park恢复,释放内存,释放sudog

close

  1. 如果chan为nil,则返回
  2. 加锁
  3. 判断是否已经关闭
  4. 把 recvq 和 sendq中的sudog取出到glist
  5. 解锁
  6. 依次ready glist

挂起和唤醒

当发送行为或者接收行为不能被满足的时候,会挂起。(并非全部,也有非阻塞实现,先跳过)

当以上行为得到满足的时候,会被唤醒

发送的时候如果已满或者没有缓存的时候,会把g包装成sudog,然后放到chan中,park,等有recv来之后,会把发送的sudog取出,执行goready。(接受同理)

几种常见行为

行为 表现
发送时buf满了 阻塞
接受时buf没数据 阻塞
发送是chan close了 panic
接收时chan close了 返回默认值

Select

编译器翻译

<img src="http://picgo.vipkk.work/20200506002833.png" alt="image-20200506002833134" style="zoom:50%;" />

<img src="http://picgo.vipkk.work/20200506002932.png" alt="image-20200506002932143" style="zoom:50%;" />

Code

selectgo是 select中多个case的实现,具体分析如下

// selectgo implements the select statement.
//
// cas0 points to an array of type [ncases]scase, and order0 points to
// an array of type [2*ncases]uint16. Both reside on the goroutine's
// stack (regardless of any escaping in selectgo).
//
// selectgo returns the index of the chosen scase, which matches the
// ordinal position of its respective select{recv,send,default} call.
// Also, if the chosen scase was a receive operation, it reports whether
// a value was received.
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
    return 0,false
}

流程

  1. nil处理

  2. shuffle打乱(保证case的随机性)

    // generate permuted order
     for i := 1; i < ncases; i++ {
         j := fastrandn(uint32(i + 1))
         pollorder[i] = pollorder[j]
         pollorder[j] = uint16(i)
     }
    
  3. 按照hchan 的address进行heap sort 得到lockorder, 这个顺序是对channel加锁的顺序 防止死锁

  4. 按照lockorder的顺序依次对 channel加锁

  5. 按照pollorder顺序依次遍历ncases

    1. 如果为nil则继续
    2. 接收 - > recv
    3. 发送 -> send
    4. default

    如果都没有的话则继续

  6. 按照lock顺序把所有的chan放到对应的 等待队列

  7. park等待唤醒

  8. 继续加锁

  9. 把未成功执行的chan 出队列

  10. 解锁

参考资料

  1. go内存模型 https://golang.org/ref/mem

    https://www.pengrl.com/p/34119/ 中文翻译

问题

  1. releasetime

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:lucasgao

查看原文:go channel

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

701 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传