2021-04-25

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的流程如下图:
在这里插入图片描述
先获取全局锁;
然后enqueue元素(通过移动拷贝的方式);
释放锁;
G2读取时候作用于底层数据结构流程如下图所示:
在这里插入图片描述
先获取全局锁;
然后dequeue元素(通过移动拷贝的方式);
释放锁;
上面的读写思路其实很简单,除了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里面写入数据时候,会发生什么呢?如下图:
在这里插入图片描述
上图流程大概如下:

当前goroutine(G1)会调用gopark函数,将当前协程置为waiting状态;
将M和G1绑定关系断开;
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操作,读取前和读取后的变化图如下图:
在这里插入图片描述
整个过程如下:

G2调用 t:=<-ch 获取一个元素;
从channel的buffer里面取出一个元素task1;
从sender等待队列里面pop一个sudog;
将task4复制buffer中task1的位置,然后更新buffer的sendx和recvx索引值;
这时候需要将G1置为Runable状态,表示G1可以恢复运行;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用python中的pymsql完成如下:表结构与数据创建 1. 建立 `users` 表和 `orders` 表。 `users` 表有用户ID、用户名、年龄字段,(id,name,age) `orders` 表有订单ID、订单日期、订单金额,用户id字段。(id,order_date,amount,user_id) 2 两表的id作为主键,`orders` 表用户id为users的外键 3 插入数据 `users` (1, '张三', 18), (2, '李四', 20), (3, '王五', 22), (4, '赵六', 25), (5, '钱七', 28); `orders` (1, '2021-09-01', 500, 1), (2, '2021-09-02', 1000, 2), (3, '2021-09-03', 600, 3), (4, '2021-09-04', 800, 4), (5, '2021-09-05', 1500, 5), (6, '2021-09-06', 1200, 3), (7, '2021-09-07', 2000, 1), (8, '2021-09-08', 300, 2), (9, '2021-09-09', 700, 5), (10, '2021-09-10', 900, 4); 查询语句 1. 查询订单总金额 2. 查询所有用户的平均年龄,并将结果四舍五入保留两位小数。 3. 查询订单总数最多的用户的姓名和订单总数。 4. 查询所有不重复的年龄。 5. 查询订单日期在2021年9月1日至9月4日之间的订单总金额。 6. 查询年龄不大于25岁的用户的订单数量,并按照降序排序。 7. 查询订单总金额排名前3的用户的姓名和订单总金额。 8. 查询订单总金额最大的用户的姓名和订单总金额。 9. 查询订单总金额最小的用户的姓名和订单总金额。 10. 查询所有名字中含有“李”的用户,按照名字升序排序。 11. 查询所有年龄大于20岁的用户,按照年龄降序排序,并只显示前5条记录。 12. 查询每个用户的订单数量和订单总金额,并按照总金额降序排序。
06-03

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值