Golang-Channel原理解析

本文详细分析了Golang中Channel的实现原理,包括其使用实例、源码分析。讨论了make channel、发送与接收数据的过程,以及channel的主要特性。在源码层面,探讨了channel数据存储结构、创建channel、goroutine如何从channel接收和发送数据,以及关闭channel的流程。通过对Golang并发基础组件Channel的深入理解,有助于提升并发编程能力。
摘要由CSDN通过智能技术生成

本文主要分析golang实现并发基础组件channel的实现原理;
主要内容分为几个部分
Section1:channel使用实例分析
Section2:源码分析

Section1 channel使用实例

channel主要是为了实现go的并发特性,用于并发通信的,也就是在不同的协程单元goroutine之间同步通信。

下面主要从三个方面来讲解:

  • make channel,主要也就是hchan的数据结构原型;
  • 发送和接收数据时,goroutine会怎么调度;
  • 设计思考;

1.1 make channel

我们创建channel时候有两种,一种是带缓冲的channel一种是不带缓冲的channel。创建方式分别如下:

// buffered
ch := make(chan Task, 3)
// unbuffered
ch := make(chan int)

buffered channel

如果我们创建一个带buffer的channel,底层的数据模型如下图:
在这里插入图片描述
当我们向channel里面写入数据时候,会直接把数据存入circular queue(send)。当Queue存满了之后就会是如下的状态:
在这里插入图片描述

当dequeue一个元素时候,如下所示:
在这里插入图片描述
从上图可知,recvx自增加一,表示出队了一个元素,其实也就是循环数组实现FIFO语义。

那么还有一个问题,当我们新建channel的时候,底层创建的hchan数据结构是在哪里分配内存的呢?其实Section2里面源码分析时候已经做了分析,hchan是在heap里面分配的。

如下图所示:
在这里插入图片描述
当我们使用make去创建一个channel的时候,实际上返回的是一个指向channel的pointer,所以我们能够在不同的function之间直接传递channel对象,而不用通过指向channel的指针。

1.2 sends and receives

不同goroutine在channel上面进行读写时,涉及到的过程比较复杂,比如下图:
在这里插入图片描述
上图中G1会往channel里面写入数据,G2会从channel里面读取数据。

G1作用于底层hchan的流程如下图:
在这里插入图片描述

  1. 先获取全局锁;
  2. 然后enqueue元素(通过移动拷贝的方式);
  3. 释放锁;

G2读取时候作用于底层数据结构流程如下图所示:
在这里插入图片描述

  1. 先获取全局锁;
  2. 然后dequeue元素(通过移动拷贝的方式);
  3. 释放锁;

上面的读写思路其实很简单,除了hchan数据结构外,不要通过共享内存去通信;而是通过通信(复制)实现共享内存。

写入满channel的场景

如下图所示:channel写入3个task之后队列已经满了,这时候G1再写入第四个task的时候会发生什么呢?
在这里插入图片描述
G1这时候会暂停直到出现一个receiver。

这个地方需要介绍一下Golang的scheduler的。我们知道goroutine是用户空间的线程,创建和管理协程都是通过Go的runtime,而不是通过OS的thread。

但是Go的runtime调度执行goroutine却是基于OS thread的。如下图:
在这里插入图片描述

具体关于golang的scheduler的原理,可以看前面的一篇博客,关于go的scheduler原理分析。

当向已经满的channel里面写入数据时候,会发生什么呢?如下图:
在这里插入图片描述

上图流程大概如下:

  1. 当前goroutine(G1)会调用gopark函数,将当前协程置为waiting状态;
  2. 将M和G1绑定关系断开;
  3. scheduler会调度另外一个就绪态的goroutine与M建立绑定关系,然后M 会运行另外一个G。

所以整个过程中,OS thread会一直处于运行状态,不会因为协程G1的阻塞而阻塞。最后当前的G1的引用会存入channel的sender队列(队列元素是持有G1的sudog)。

那么blocked的G1怎么恢复呢?当有一个receiver接收channel数据的时候,会恢复 G1。

实际上hchan数据结构也存储了channel的sender和receiver的等待队列。数据原型如下:
在这里插入图片描述
等待队列里面是sudog的单链表,sudog持有一个G代表goroutine对象引用,elem代表channel里面保存的元素。当G1执行ch<-task4的时候,G1会创建一个sudog然后保存进入sendq队列,实际上hchan结构如下图:
在这里插入图片描述

这个时候,如果G1进行一个读取channel操作,读取前和读取后的变化图如下图:

  • 39
    点赞
  • 131
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值