Go中Channel发送和领受独霸指南

焦点 · 2026-07-20 19:32:10

序言

先来看一道面试题:

对已封锁的发送 chan 举办读写 ,会若何样?和领为甚么 ?

在上一篇进修 Go 协程的文章中  ,晓得 go 关头字可以用来开启一个 goroutine 举办义务措置,受独但多个义务之间假定需求通信 ,发送就需求用到通道(channel)了  。和领

1 、受独Channel的发送定义

声明并初始化一个通道,可独霸 Go 言语的和领内建函数 make  ,同时指定该通道圭表类型的受独元素圭表类型 ,上面声了然一个 chan int 圭表类型的发送 channel:

ch := make(chan int)

2 、Channel的和领独霸

发送(写):发送独霸包含了“复制元素值”和“放置副本到通道内部”这两个法度圭表类型 。即:进进通道的受独真实不是独霸符右边的阿谁元素值 ,而是发送它的副本。

ch := make(chan int)// write to channelch <- x

领受(读):领受独霸包含了“复制通道内的和领元素值” 、“放置副本到领受方”、受独“删损掉落踪落原值”三个法度圭表类型 。

ch := make(chan int)// read from channelx <- ch// another way to readx = <- ch

封锁 :封锁 channel 会产生发火一个播送机制 ,全数向 channel 读感消息的 goroutine 都邑收到消息 。

ch := make(chan int)close(ch)

从一个已封锁的 channel 中读感消息永远不会梗阻 ,并且会前去一个为 false 的 ok-idiom,可以用它来剖断 channel 可否封锁  :

v, ok := <-ch

假定 ok 是false,注解领受的 v 是产生发火的零值 ,这个 channel 被封锁了或为空。

3 、Channel发送和领受独霸的特点

  1. 一个通道相当于一个进步先辈先出(FIFO)的行列:也就是说,通道中的各个元素值都是严格地屈就发送的按序列举的 ,先被发送通道的元素值必定会先被领受。
  2. 对不合个通道,发送独霸之间和领受独霸之间是互斥的:不应时辰,对不合通道发送多个元素 ,直到这个元素值被无缺复制进该通道此后,其他针对该通道的发送独霸才大年夜大年夜约被奉行。领受也是如斯。
  3. 发送独霸和领受独霸中 ,对元素值的措置是不成豆割的:后面我们晓得发送一个值到通道 ,是先复制值 ,再将该副本挪动到通道内部 ,“不成豆割”指的是发送独霸要么还没复制元素值 ,要么已复制终了 ,尽不会展示只复制了一部分的气候 。领受也是同理,在筹搞妥元素值的副本此后 ,必定会删除损掉落踪落通道中的原值,尽不会展示通道中仍有残留的气候 。
  4. 发送独霸和领受独霸在无缺完成之前会被梗阻:发送独霸包含了“复制元素值”和“放置副本到通道内部”这两个法度圭表类型。在这两个法度圭表类型无缺完成之前 ,建议这个发送独霸的那句代码会不时梗阻在那儿何处 ,在它此后的代码不会有奉行的机会,直到梗阻消弭  。

4、Channel的圭表类型

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

独霸 make 声明一个通道圭表类型变量时 ,除指定通道的元素圭表类型 ,还可以指定通道的容量,也就是通道最多可以缓存若干很多若干良多若干很多若干良多若干很多若干个元素值,当容量为 0 时 ,该通道为非缓冲通道 ,当容量除夜于 0 时 ,该通道为带有缓冲的通道 。

ch := make(chan int)    //无缓冲的channelch := make(chan int, 3) //带缓冲的channel

非缓冲通道重冲要通道有着不合的数据传递编制  :

  • 非缓冲通道:非论是发送独霸仍是领受独霸 ,一最早奉行就会被梗阻,直到配对的独霸也最早奉行,才会延续传递 。即 :只需收发单方对接上了  ,数据才会被传递 。数据直接从发送方复制到领受方 。非缓冲通道传递数据的编制是同步的。
  • 缓冲通道 :假定通道已满 ,对它的全数发送独霸都邑被梗阻 ,直到通道中有元素值被领受走 。反之,假定通道已空  ,那么对它的全数领受独霸都邑被梗阻 ,直到通道中有新的元素值展示。元素值会先从发送方复制到缓冲通道,此后再由缓冲通道复制给领受方。缓冲通道传递数据的编制是异步的。

5 、Channel的源码进修

Channel 的次要完成在 src/runtime/chan.go 中 ,go 版本为 go version go1.14.6 darwin/amd64这里次要看 chansend 若何完成的。

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {  if c == nil {   if !block {    return false  }  gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)  throw("unreachable") } if debugChan {   print("chansend: chan=", c, "\n") } if raceenabled {   racereadpc(c.raceaddr(), callerpc, funcPC(chansend)) } // Fast path: check for failed non-blocking operation without acquiring the lock. // // After observing that the channel is not closed, we observe that the channel is // not ready for sending. Each of these observations is a single word-sized read // (first c.closed and second c.recvq.first or c.qcount depending on kind of channel). // Because a closed channel cannot transition from 'ready for sending' to // 'not ready for sending', even if the channel is closed between the two observations, // they imply a moment between the two when the channel was both not yet closed // and not ready for sending. We behave as if we observed the channel at that moment, // and report that the send cannot proceed. // // It is okay if the reads are reordered here: if we observe that the channel is not // ready for sending and then observe that it is not closed, that implies that the // channel wasn't closed during the first observation. if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||  (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {   return false } var t0 int64 if blockprofilerate > 0 {   t0 = cputicks() } lock(&c.lock) if c.closed != 0 {   unlock(&c.lock)  panic(plainError("send on closed channel")) } if sg := c.recvq.dequeue(); sg != nil {   // Found a waiting receiver. We pass the value we want to send  // directly to the receiver, bypassing the channel buffer (if any).  send(c, sg, ep, func() {  unlock(&c.lock) }, 3)  return true } if c.qcount < c.dataqsiz {   // Space is available in the channel buffer. Enqueue the element to send.  qp := chanbuf(c, c.sendx)  if raceenabled {    raceacquire(qp)   racerelease(qp)  }  typedmemmove(c.elemtype, qp, ep)  c.sendx++  if c.sendx == c.dataqsiz {    c.sendx = 0  }  c.qcount++  unlock(&c.lock)  return true } if !block {   unlock(&c.lock)  return false } // Block on the channel. Some receiver will complete our operation for us. gp := getg() mysg := acquireSudog() mysg.releasetime = 0 if t0 != 0 {   mysg.releasetime = -1 } // No stack splits between assigning elem and enqueuing mysg // on gp.waiting where copystack can find it. mysg.elem = ep mysg.waitlink = nil mysg.g = gp mysg.isSelect = false mysg.c = c gp.waiting = mysg gp.param = nil c.sendq.enqueue(mysg) gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2) // Ensure the value being sent is kept alive until the // receiver copies it out. The sudog has a pointer to the // stack object, but sudogs aren't considered as roots of the // stack tracer. KeepAlive(ep) // someone woke us up. if mysg != gp.waiting {   throw("G waiting list is corrupted") } gp.waiting = nil gp.activeStackChans = false if gp.param == nil {   if c.closed == 0 {    throw("chansend: spurious wakeup")  }  panic(plainError("send on closed channel")) } gp.param = nil if mysg.releasetime > 0 {   blockevent(mysg.releasetime-t0, 2) } mysg.c = nil releaseSudog(mysg) return true}

从代码中可以看到 :

  • 有 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发送和领受内容请搜刮完竣下载之前的文章或延续不雅不雅不雅不雅鉴赏上面的相干文章希看大年夜师往后多多支撑完竣下载!

文章推荐:

王者名望净化图片高清放除夜

黑甜乡新诛仙兽神四阶段攻略打法

原神凝光禀赋晋级材料一览除夜全

小浣熊百将传单除夜爷答题谜底除夜全

鬼泣岑岭之战张除夜仙抽奖口令最新除夜全

明日之跋文忆奉求阶段义务攻略

斗罗除夜陆武魂省悟炼狱修罗4

斗罗除夜陆武魂省悟炼狱之章第四章攻略除夜全

摩尔庄园手游的小红猪载具若何掉落踪掉落踪

明灭热热精灵忖量搭配主题攻略

王者名望除夜仙杯100京东卡独霸编制

原神凝光禀赋晋级材料一览除夜全