golang 源码分析04 channel

https://www.cnblogs.com/ricklz/p/13813201.html

看golang的源码 chan.go 即channel的实现

     不要通过共享内存来通信,⽽应通过通信来共享内存。 这是更⾼层次的并发编程哲学(通过管道来传值是Go语⾔推荐的做法)。虽然像引⽤计数这类简单的并 发问题通过原⼦操作或互斥锁就能很好地实现,但是通过Channel来控制访问能够让你写出更简洁正确的程序。

channel

Golang中使用 CSP中 channel 这个概念。channel 是被单独创建并且可以在进程之间传递,它的通信模式类似于 boss-worker 模式的,一个实体通过将消息发送到channel 中,然后又监听这个 channel 的实体处理,两个实体之间是匿名的,这个就实现实体中间的解耦,其中 channel 是同步的一个消息被发送到 channel 中,最终是一定要被另外的实体消费掉的。

channel的定义

channel 是一个引用类型,所以在它被初始化之前,它的值是 nil,channel 使用 make 函数进行初始化。go中内置的类型,初始化的时候,我们需要初始化channel的长度。

有缓冲和无缓冲的差别是什么呢?

对不带缓冲的 channel 进行的操作实际上可以看作“同步模式”,带缓冲的则称为“异步模式”。

同步模式下,发送方和接收方要同步就绪,只有在两者都 ready 的情况下,数据才能在两者间传输(后面会看到,实际上就是内存拷贝)。否则,任意一方先行进行发送或接收操作,都会被挂起,等待另一方的出现才能被唤醒。

异步模式下,在缓冲槽可用的情况下(有剩余容量),发送和接收操作都可以顺利进行。否则,操作的一方(如写入)同样会被挂起,直到出现相反操作(如接收)才会被唤醒。

举个栗子:

无缓冲的 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。

无缓冲保证信能到你手上

有缓冲的 就是一个送信人去你家仍到你家的信箱 转身就走 ,除非你的信箱满了 他必须等信箱空下来。

有缓冲的 保证 信能进你家的邮箱

hchan中的所有属性大致可以分为三类:

1. buffer相关的属性。例如buf、dataqsiz、qcount等。 当channel的缓冲区大小不为0时,buffer中存放了待接收的数据。使用ring buffer实现。

2. waitq相关的属性,可以理解为是一个FIFO的标准队列。其中recvq中是正在等待接收数据的goroutine,sendq中是等待发送数据的goroutine。waitq使用双向链表实现。

3. 其他属性,例如lock、elemtype、closed等。

  buf指向底层的循环数组,只有缓冲类型的channel才有。

  sendx,recvx 均指向底层循环数组,表示当前可以发送和接收的元素位置索引值(相对于底层数组)。

  sendq,recvq 分别表示被阻塞的 goroutine,这些 goroutine 由于尝试读取 channel 或向 channel 发送数据而被阻塞。读的时候,如果循环数据为空,那么当前读的goroutine就会加入到recvq,等待有消息写入结束阻塞。同理写入的goroutine,一样,如果队列满了,就加入到sendq,阻塞直到消息写入。

waitq 相关的属性,可以理解为是一个 FIFO 的标准队列。其中 recvq 中是正在等待接收数据的 goroutine,sendq 中是等待发送数据的 goroutine。waitq 使用双向链表实现。

recvq和sendq,它们是 waitq 结构体,而waitq实际上就是一个双向链表,链表的元素是sudog,里面包含 g 字段,g 表示一个 goroutine,所以 sudog 可以看成一个 goroutine。但是两个还是有区别的。

lock通过互斥锁保证数据安全。

设计思路:

对于无缓冲的是没有buf,有缓冲的buf是有buf的,长度也就是创建channel制定的长度。

有缓冲channel的buf是循环使用的,已经读取过的,会被后面新写入的消息覆盖,通过sendx,recvx这两个指向底层数据的指针的滑动,实现对buf的复用。

具体的消息写入读读取,以及goroutine的阻塞,请看下面

环形队列

chan内部实现了一个环形队列作为其缓冲区,队列的长度是创建chan时指定的。

看下实现的图片:

下面先看下makechan方法,新建channel

1.先校验elem的size,不能大于65536,如果大于,就抛出异常

2.继续校验分配内存不能过大

3.如果channel的缓冲区大小为0,就给hchan分配一个空间

4.如果类型不是指针,那么就给hchan和buf 都分配空间

5.默认情况下,也就是包含指针,会给hchan和buf 都分配空间

6.最后再给channel设置几个字段的值,然后返回channel

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

chansend  向channel写入数据

1.如果channel为nil,就抛出异常

2.如果不是阻塞block的,并且未关闭,并且channel没有多余的空间(没有缓冲区,有缓冲区已经满了)  , 就直接返回false

3.如果c.closed != 0 ,代表channel已经关闭了,直接抛出异常

4.如果接收队列recvq有goroutine,直接把发送的数据发给接收goroutine

5.缓冲型的channel,buffer 中已放入的元素个数小于循环数组的长度,就继续放入循环数组

6.阻塞在channel上,就不能发送了,所以把发送数据的goroutine 放入到阻塞队列sendq里面

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

读取数据 chanrecv

1.如果channel为nil,直接抛出异常

2.如果channel不带有缓冲,并且sendq没有发送队列正在等待;或者是带缓冲的channel,但是buffer中没有元素,此时就不能接收数据

3.对于有缓冲的chanenl,即使关闭了channel,buf中有元素,仍然可以接收到元素

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值