1、本文讨论Channel的底层实现原理
首先,我们看Channel的结构体
简要介绍管道结构体中,几个关键字段
在Golang中,管道是分为有缓冲区的管道和无缓冲区的管道。
这里简单提一下,缓冲区大小为1的管道和无缓冲区的管道的区别,如果是
缓冲区大小为1的管道,发送者A发送数据后,发送者A可以去干其它行为,但是别的发送者想发送数据,会阻塞。对于发送者A来说,这是一个非阻塞行为。
无缓冲区的管道的区别,发送者A发送数据后,发送者A会被阻塞,直到管道数据被读取,发送者A才会被唤醒,对于发送者A来说,这是一个阻塞行为。
回归正题,buf是一个指针变量,当有缓冲区时,指向保存数据的底层数组,sendx指的是,当前管道发送数据的最后位置,recvx是当前管道接收数据的最后一个位置,其实这样就构成了一个循环队列,如图
recvq是等待接收队列,该队列实现的数据结构是双链表,链表节点可以理解成协程,保存的是被管道阻塞的协程。
sendq是等待发送队列,该队列实现的数据结构是双链表,链表节点可以理解成协程,保存的是被管道阻塞的协程。
其实管道可以理解成数据通信方式中的共享空间,对于共享空间的变量读写,为了保证并发数据的一致性,所以需要加锁,所以lock字段就是锁,而且是悲观锁。
所以 管道 收发数据是要加锁的
再给大家分享一些关于管道的理解吧
(1)当向未关闭,但是缓冲区已满的管道发送数据,或者缓冲区为空的管道读取数据就会阻塞,阻塞的协程加入感到的发送等待队列或接收等待队列
(2)使用channel的时候,也需要注意管道是否会发生gorountine泄漏,比如一个G****一直阻塞在一个不会改变状态的管道的时候,这个G不会结束,这个G的内存空间就无法回收。一个G的内存空间大概就几KB,但是还是取决于你的栈有多大。
(3)包括关闭channel的时候,关闭channel的原则主要有
----①不要向已关闭的管道发送数据
----②不要关闭已关闭的管道
----③所以,关闭管道主要有以下几个场景
--------1)如果是一个发送者,都让发送者关闭
--------2)如果是多个发送者,一个接收者
----------------a.可以是用一个传递信号的管道,让每个发送者去for+select去监听,然后接收者负责关闭 传递信号 的管道。当接收者关闭这个管道,发送者那边select的case最终肯定会进入,然后让发送者退出。这里面并不去实际关闭传送数据的管道
--------3)如果是多个发送者,多个接收者
----------------a.可以用一个中间人,去接收关闭信号,然后去关闭传递信号的管道,关闭后这个中间人协程就退出。任一发送者、接收者都可以发送关闭的信号,全部发送者、接收者都for+select监听关闭信号的管道,和多个发送者、一个接收者类型,通过中间人 只关闭一次,避免了重复关闭导致的Panic
-------4)如果多个发送者都由一个父协程产生的,那就让父协程来关闭