请选择 进入手机版 | 继续访问电脑版

[Golang] Go中Channel发送和吸收 操作指南

[复制链接]
查看39 | 回复10 | 2021-9-15 01:56:14 | 显示全部楼层 |阅读模式
目次

媒介

先来看一道口试 题:

  1. 对已经关闭的 chan 进行读写,会怎么样?为什么?
复制代码

在上一篇学习 Go 协程的文章中,知道 go 关键字可以用来开启一个 goroutine 进利用 命 处理,但多个任务 之间假如 必要 通讯 ,就必要 用到通道(channel)了。

一、Channel的定义

声明并初始化一个通道,可以利用 Go 语言的内建函数 make,同时指定该通道范例 的元素范例 ,下面声明白 一个 chan int 范例 的 channel:

  1. ch := make(chan int)
复制代码

二、Channel的操作

发送(写):发送操作包括了“复制元素值”和“放置副本到通道内部”这两个步骤。即:进入通道的并不是操作符右边的谁人 元素值,而是它的副本。

  1. ch := make(chan int)
  2. // write to channel
  3. ch <- x
复制代码

吸收 (读):吸收 操作包含了“复制通道内的元素值”、“放置副本到吸收 方”、“删掉原值”三个步骤。

  1. ch := make(chan int)
  2. // read from channel
  3. x <- ch
  4. // another way to read
  5. x = <- ch
复制代码

关闭:关闭 channel 会产生一个广播机制,全部 向 channel 读取消息的 goroutine 都会收到消息。

  1. ch := make(chan int)
  2. close(ch)
复制代码

从一个已关闭的 channel 中读取消息永久 不会壅闭 ,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭:

  1. v, ok := <-ch
复制代码

假如 ok 是false,表明吸收 的 v 是产生的零值,这个 channel 被关闭了或者为空。

三、Channel发送和吸收 操作的特点

  1. 一个通道相称 于一个先辈 先出(FIFO)的队列:也就是说,通道中的各个元素值都是严酷 地按照发送的次序 分列 的,先被发送通道的元素值肯定 会先被吸收 。
  2. 对于同一个通道,发送操作之间和吸收 操作之间是互斥的:同一时候 ,对同一通道发送多个元素,直到这个元素值被完全复制进该通道之后,其他针对该通道的发送操作才大概 被实行 。吸收 也是云云 。
  3. 发送操作和吸收 操作中,对元素值的处理是不可分割的:前面我们知道发送一个值到通道,是先复制值,再将该副本移动到通道内部,“不可分割”指的是发送操作要么还没复制元素值,要么已经复制完毕,绝不会出现只复制了一部分的环境 。吸收 也是同理,在预备 好元素值的副本之后,肯定 会删除掉通道中的原值,绝不会出现通道中仍有残留的环境 。
  4. 发送操作和吸收 操作在完全完成之前会被壅闭 :发送操作包括了“复制元素值”和“放置副本到通道内部”这两个步骤。在这两个步骤完全完成之前,发起这个发送操作的那句代码会不停 壅闭 在那边 ,在它之后的代码不会有实行 的机会,直到壅闭 排除 。

四、Channel的范例

channel 分为不带缓存的 channel 和带缓存的 channel。

利用 make 声明一个通道范例 变量时,除了指定通道的元素范例 ,还可以指定通道的容量,也就是通道最多可以缓存多少个元素值,当容量为 0 时,该通道为非缓冲通道,当容量大于 0 时,该通道为带有缓冲的通道。

  1. ch := make(chan int) //无缓冲的channel
  2. ch := make(chan int, 3) //带缓冲的channel
复制代码

非缓冲通道和缓冲通道有着不同的数据传递方式:

  • 非缓冲通道:无论是发送操作还是吸收 操作,一开始实行 就会被壅闭 ,直到配对的操作也开始实行 ,才会继续传递。即:只有收发双方 对接上了,数据才会被传递。数据直接从发送方复制到吸收 方。非缓冲通道传递数据的方式是同步的。
  • 缓冲通道:假如 通道已满,对它的全部 发送操作都会被壅闭 ,直到通道中有元素值被吸收 走。反之,假如 通道已空,那么对它的全部 吸收 操作都会被壅闭 ,直到通道中有新的元素值出现。元素值会先从发送方复制到缓冲通道,之后再由缓冲通道复制给吸收 方。缓冲通道传递数据的方式是异步的。

五、Channel的源码学习

Channel 的紧张 实现在 src/runtime/chan.go 中,go 版本为 go version go1.14.6 darwin/amd64这里紧张 看 chansend 怎样 实现的。

  1. func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
  2. if c == nil {
  3. if !block {
  4. return false
  5. }
  6. gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
  7. throw("unreachable")
  8. }
  9. if debugChan {
  10. print("chansend: chan=", c, "\n")
  11. }
  12. if raceenabled {
  13. racereadpc(c.raceaddr(), callerpc, funcPC(chansend))
  14. }
  15. // Fast path: check for failed non-blocking operation without acquiring the lock.
  16. //
  17. // After observing that the channel is not closed, we observe that the channel is
  18. // not ready for sending. Each of these observations is a single word-sized read
  19. // (first c.closed and second c.recvq.first or c.qcount depending on kind of channel).
  20. // Because a closed channel cannot transition from 'ready for sending' to
  21. // 'not ready for sending', even if the channel is closed between the two observations,
  22. // they imply a moment between the two when the channel was both not yet closed
  23. // and not ready for sending. We behave as if we observed the channel at that moment,
  24. // and report that the send cannot proceed.
  25. //
  26. // It is okay if the reads are reordered here: if we observe that the channel is not
  27. // ready for sending and then observe that it is not closed, that implies that the
  28. // channel wasn't closed during the first observation.
  29. if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
  30. (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
  31. return false
  32. }
  33. var t0 int64
  34. if blockprofilerate > 0 {
  35. t0 = cputicks()
  36. }
  37. lock(&c.lock)
  38. if c.closed != 0 {
  39. unlock(&c.lock)
  40. panic(plainError("send on closed channel"))
  41. }
  42. if sg := c.recvq.dequeue(); sg != nil {
  43. // Found a waiting receiver. We pass the value we want to send
  44. // directly to the receiver, bypassing the channel buffer (if any).
  45. send(c, sg, ep, func() { unlock(&c.lock) }, 3)
  46. return true
  47. }
  48. if c.qcount < c.dataqsiz {
  49. // Space is available in the channel buffer. Enqueue the element to send.
  50. qp := chanbuf(c, c.sendx)
  51. if raceenabled {
  52. raceacquire(qp)
  53. racerelease(qp)
  54. }
  55. typedmemmove(c.elemtype, qp, ep)
  56. c.sendx++
  57. if c.sendx == c.dataqsiz {
  58. c.sendx = 0
  59. }
  60. c.qcount++
  61. unlock(&c.lock)
  62. return true
  63. }
  64. if !block {
  65. unlock(&c.lock)
  66. return false
  67. }
  68. // Block on the channel. Some receiver will complete our operation for us.
  69. gp := getg()
  70. mysg := acquireSudog()
  71. mysg.releasetime = 0
  72. if t0 != 0 {
  73. mysg.releasetime = -1
  74. }
  75. // No stack splits between assigning elem and enqueuing mysg
  76. // on gp.waiting where copystack can find it.
  77. mysg.elem = ep
  78. mysg.waitlink = nil
  79. mysg.g = gp
  80. mysg.isSelect = false
  81. mysg.c = c
  82. gp.waiting = mysg
  83. gp.param = nil
  84. c.sendq.enqueue(mysg)
  85. gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
  86. // Ensure the value being sent is kept alive until the
  87. // receiver copies it out. The sudog has a pointer to the
  88. // stack object, but sudogs aren't considered as roots of the
  89. // stack tracer.
  90. KeepAlive(ep)
  91. // someone woke us up.
  92. if mysg != gp.waiting {
  93. throw("G waiting list is corrupted")
  94. }
  95. gp.waiting = nil
  96. gp.activeStackChans = false
  97. if gp.param == nil {
  98. if c.closed == 0 {
  99. throw("chansend: spurious wakeup")
  100. }
  101. panic(plainError("send on closed channel"))
  102. }
  103. gp.param = nil
  104. if mysg.releasetime > 0 {
  105. blockevent(mysg.releasetime-t0, 2)
  106. }
  107. mysg.c = nil
  108. releaseSudog(mysg)
  109. return true
  110. }
复制代码

从代码中可以看到:

  • 有 goroutine 壅闭 在 channel recv 队列上,此时缓存队列为空,直接将消息发送给 reciever goroutine,只产生一次复制。
  • 当 channel 缓存队列有剩余空间时,将数据放到队列里,等待吸收 ,吸收 后统共 产生两次复制。
  • 当 channel 缓存队列已满时,将当前 goroutine 加入 send 队列并壅闭 。

以是 ,开头的口试 题就有了答案:

读:

读已经关闭的 chan,能不停 读到内容,但是读到的内容根据通道内关闭前是否有元素而不同。

假如 chan 关闭前,buffer 内有元素还未读,会正确 读到 chan 内的值,且返回的第二个 bool 值为 true;

假如 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,返回 channel 元素的零值,第二个 bool 值为 false。

写:

写已经关闭的 chan 会 panic。

总结

到此这篇关于Go中Channel发送和吸收 操作的文章就先容 到这了,更多相干 Go Channel发送和吸收 内容请搜刮 脚本之家从前 的文章或继续欣赏 下面的相干 文章渴望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar 秦基兴拍 | 2021-9-15 03:20:53 | 显示全部楼层
这个帖子好无聊啊!
回复

使用道具 举报

avatar 韭菜555 | 2021-9-16 10:59:56 | 显示全部楼层
最近回了很多帖子,都没人理我!
回复

使用道具 举报

avatar 刘和谐1 | 2021-9-21 07:36:13 | 显示全部楼层
有内涵!
回复

使用道具 举报

avatar 李志敏 | 2021-9-22 06:36:31 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的源码论坛他的站点都是商业源码,还是免费下载的那种!特别好用。访问地址:http://www.mxswl.com 猫先森网络
回复

使用道具 举报

avatar ggp27 | 2021-10-9 07:16:59 | 显示全部楼层
很给力!
回复

使用道具 举报

avatar 123456881 | 2021-10-11 15:35:40 | 显示全部楼层
楼上的这是啥态度呢?
回复

使用道具 举报

很多天不上线,一上线就看到这么给力的帖子!
回复

使用道具 举报

admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的源码论坛他的站点都是商业源码,还是免费下载的那种!特别好用。访问地址:http://www.mxswl.com 猫先森网络
回复

使用道具 举报

admin楼主今年多大了?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则