channel原来就是个环形队列
golang有一个很重要的特性就是channel,经常配合goroutine一起使用。一、基本用法初始化ch := make(chan bool)发送数据ch <- x接受数据x := <- ch x,ok := <- ch
当然,其中也涉及到有缓冲和无缓冲的情况,为什么会造成这种情况,我们会在下面解释。二、数据结构type hchan struct { qcount uint // 队列中数据的个数 dataqsiz uint // 队列容量 buf unsafe.Pointer // 存放在环形数组的数据 elemsize uint16 // channel中数据类型大小 closed uint32 // channel是否关闭 elemtype *_type // 元素类型 sendx uint // send的数组索引 recvx uint // receive的数组索引 recvq waitq // <-ch 阻塞在chan上的队列 list of recv waiters sendq waitq // ch<- 阻塞在chan上的队列 list of send waiters lock mutex }
channel的数据结构不太复杂,就是一个环形队列,里面保存了长度qcount,容量dataqsiz,数据buf,以及前后索引sendx,recvx。
closed用来标识channel的状态,0表示未关闭,非0表示已关闭,如果关闭,那么就不能发送数据。三、初始化
在内部有两个make函数,一个是makechan64,一个是makechan,其实makechan64本质上还是调用的makechan。1、长度判断elem := t.elem if elem.size >= 1<<16 { throw("makechan: invalid channel element type") } if hchanSize%maxAlign != 0 || elem.align > maxAlign { throw("makechan: bad alignment") } mem, overflow := math.MulUintptr(elem.size, uintptr(size)) if overflow || mem > maxAlloc-hchanSize || size < 0 { panic(plainError("makechan: size out of range")) }
初始化的时候可以传入长度size,然后根据你初始化数据的类型大小elem.size计算是否有可用空间。2、分配内存var c *hchan switch { case mem == 0: c = (*hchan)(mallocgc(hchanSize, nil, true)) c.buf = c.raceaddr() case elem.ptrdata == 0: c = (*hchan)(mallocgc(hchanSize+mem, nil, true)) c.buf = add(unsafe.Pointer(c), hchanSize) default: c = new(hchan) c.buf = mallocgc(mem, elem, true) }如果size为0,只分配hchanSize的大小,如果是64位就是80,如果是32位就是40如果数据类型不是指针,分配一块连续内存hchanSize+mem如果数据类型是指针,hchan和buf单独分配
此时,将结构体剩余字段赋值。c.elemsize = uint16(elem.size) c.elemtype = elem c.dataqsiz = uint(size)四、send
就是ch <- x
调用的函数签名是chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool1、空chan判断
首先判断channel是否初始化if c == nil { if !block { return false } gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2) throw("unreachable") }
其次判断channel是否关闭if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) || (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) { return false }
这段判断逻辑还是比较复杂的。
这个是fast path,向没有阻塞的管道判断发送失败,这样就可以不用获取锁进行判断了。
如果是非阻塞,管道没有关闭的情况下,没有缓冲区或缓冲区已经满了,返回false。
由于这里是并发执行的,可能会在判断完c.closed==0之后,关闭channel,那么这里会出现这两种情况:
1、channel没有关闭,没有缓冲区或缓冲区已经满了,返回false
2、channel已经关闭,close会加锁将recvq和sendq全部出队列,返回false
所以这里的判断是十分严谨的。2、加锁lock(&c.lock) if c.closed != 0 { unlock(&c.lock) panic(plainError("send on closed channel")) }3、取出接受者if sg := c.recvq.dequeue(); sg != nil { send(c, sg, ep, func() { unlock(&c.lock) }, 3) return true }
找到一个等待的接受者,直接发送4、是否有缓冲
如果没有找到有等待的接受者,那么就看channel是否是有缓冲的。if c.qcount < c.dataqsiz { qp := chanbuf(c, c.sendx) typedmemmove(c.elemtype, qp, ep) c.sendx++ if c.sendx == c.dataqsiz { c.sendx = 0 } c.qcount++ unlock(&c.lock) return true }
可以看到环形队列的判断
如果到这里,前面的步骤都没有发送成功,表示没有接受者等待,也没有缓冲区,那么就需要挂起goroutine等待接受者了。if !block { unlock(&c.lock) return false }5、阻塞goroutinegp := getg() mysg := acquireSudog() mysg.releasetime = 0 if t0 != 0 { mysg.releasetime = -1 } 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) atomic.Store8(&gp.parkingOnChan, 1) gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2) KeepAlive(ep)
添加发送者到发送队列,调用gopark阻塞6、发送完成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)
goroutine被唤醒后,表示发送完成,清理现场五、recvfunc chanrecv1(c *hchan, elem unsafe.Pointer) { chanrecv(c, elem, true) }
对应的是x <- chfunc chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) { _, received = chanrecv(c, elem, true) return }
对应的是x,ok :=<- ch1、空channel判断if c == nil { if !block { return } gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2) throw("unreachable") }
同样这里也有个fast pathif !block && (c.dataqsiz == 0 && c.sendq.first == nil || c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) && atomic.Load(&c.closed) == 0 { return }
这里把是否关闭放在了最后进行判断,与发送不一样,这是因为接受的时候会走default分支c := make(chan int, 1) c <- 1 go func() { select { case <- c: println("receive from c") default: println("c is not ready") } } close(c)
这段代码应该不执行default分支。
可能会出现如下情况:select发生在close之前,从c中取出1select发送在close之后,但在<-c 之前,取出1select发送在<-c之后,取出0,received=false,但不会执行default
如果这里我们把c.closed的判断放在前面的话,会出现以下情况:通道未关闭,不存在可接受数据,没有发送者等待,返回(false, false)通道已关闭,不存在可接受数据,没有发送者等待,应该要返回(ture, false),这里返回了(false, false)
这里,selected应该为true,所以把c.closed放在最后判断可以避免这种情况。2、通道关闭lock(&c.lock) if c.closed != 0 && c.qcount == 0 { unlock(&c.lock) if ep != nil { typedmemclr(c.elemtype, ep) } return true, false }
如果通道已经关闭并且没有数据可以读取,返回(true,false)3、取出发送者if sg := c.sendq.dequeue(); sg != nil { recv(c, sg, ep, func() { unlock(&c.lock) }, 3) return true, true }
找到一个发送者,接受数据4、是否有缓冲if c.qcount > 0 { qp := chanbuf(c, c.recvx) if ep != nil { typedmemmove(c.elemtype, ep, qp) } typedmemclr(c.elemtype, qp) c.recvx++ if c.recvx == c.dataqsiz { c.recvx = 0 } c.qcount-- unlock(&c.lock) return true, true }5、阻塞goroutinegp := getg() mysg := acquireSudog() mysg.releasetime = 0 if t0 != 0 { mysg.releasetime = -1 } mysg.elem = ep mysg.waitlink = nil gp.waiting = mysg mysg.g = gp mysg.isSelect = false mysg.c = c gp.param = nil c.recvq.enqueue(mysg) atomic.Store8(&gp.parkingOnChan, 1) gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)6、接受完成if mysg != gp.waiting { throw("G waiting list is corrupted") } gp.waiting = nil gp.activeStackChans = false if mysg.releasetime > 0 { blockevent(mysg.releasetime-t0, 2) } closed := gp.param == nil gp.param = nil mysg.c = nil releaseSudog(mysg)六、关闭channel
这部分比较简单,就是加锁,设置标识位c.closed,然后唤醒所有的接受者和发送者,接受和发送数据,最后释放锁。七、总结
1、channel底层就是一个环形队列
2、在有接受者或发送者的情况下,在select中不会走default分支
3、初始化的缓冲就是对应的是否阻塞block
奥特曼收集,到目前出现过的奥特曼有哪些(2)银河奥特曼(UltramanGinga,)身高微型40米无限大体重040000吨无限大人间体礼堂光变身器银河火花绝招银河穿击光线礼堂光用神秘道具银河火花变身成的神秘英雄。一般情况下
欧洲杯历史首个两届赞助席位被vivo拿下,这次苹果三星钱给少了说起手机界的跨界,有人认为华为玩的十分通透,毕竟保时捷系列收获了一大批粉丝的心,而这次vivo用实际表现证明了自己同样也能把跨界玩出花样来!在今年6月,欧洲杯再次闯入各大球迷的眼界
前端CSS学习笔记泡泡效果效果视频加载中代码!DOCTYPEhtmlhtmllangenheadmetacharsetutf8styletypetextcss。boxwidth343pxheight100p
前端CSS学习笔记2D破碎盾牌复原过渡效果效果视频加载中代码!DOCTYPEhtmlhtmlheadlangenmetacharsetUTF8titletitlestyletypetextcssbodybackground
子弹短信火不过三个月这两天朋友圈到处都在刷屏子弹短信的二维码,这个像极了网易公开课的那些刷屏海报,能在朋友圈刷屏的除了海报h5小游戏等,还有拼多多,这次又增加了一个app子弹短信,大家看着兴趣很高涨,
李佳琦薇娅高居淘系双雄平台流量形态成金字塔?10月20日,淘宝双十一首轮预售鸣锣开卖,当日除了天量的成交规模以外,最引人注意的恐怕就是淘系两大主播李佳琦薇娅的个人战绩!当天薇娅和李佳琦的淘宝直播间一晚上的观看人数都超过了2个
玖月奇迹为什么比不过凤凰传奇?在2011年,第一次登上春晚舞台的玖月奇迹一炮而红,在大学期间,两人因喜欢音乐而结缘,并组建了玖月奇迹这个组合参加星光大道,从星光大道出名后,这两人的事业也是一路顺风顺水,并且关系
人猿星球将成现实?英伟达集齐三芯召唤神龙?图灵周报图灵周报精选AI行业一周大事件,从良莠不齐的行业资讯中挑选出最有价值的信息,配上专业点评,值得你细读品味。01微软160亿美元收购AI语音公司Nuance当地时间4月12日,微软和
不只有三变!TFC威震天多形态试模展示去年9月份跟兄弟们分享过一款TFC三变威震天的资讯,个人对这款产品还是非常感兴趣的,毕竟可以跟他们家滚雷擎天柱搭配。时隔近一年,这款产品终于有新动态了,今天就跟兄弟们简单的分享一下
共享单车涨价?资本肆虐后的穷途末路这几天共享单车涨价成了热点,继小蓝车涨价之后,从今天(日)起,摩拜单车也在北京地区实行了新的计费规则。摩拜单车起步价调整为元分钟骑行超出分钟,每分钟收费元。看来在资本肆虐过后,共享
共享单车涨价?资本肆虐后的穷途末路这几天共享单车涨价成了热点,继小蓝车涨价之后,从今天(日)起,摩拜单车也在北京地区实行了新的计费规则。摩拜单车起步价调整为元分钟骑行超出分钟,每分钟收费元。看来在资本肆虐过后,共享