Golang channel 底层实现
貌似是我开始学到到go语言channel时,第一句映入我眼帘的话,也是我目前印象很深刻的一句话——“我们不要通过共享内存来实现通讯,而是通过通讯来共享内存”
1、channel应用场景
1)输出数据给其它使用方(协程间通信)
2)组合多个逻辑(channel+select 实现组合逻辑)
2、channel的重要的数据结构
可以看一下源码,channel底层数据结构的结构体有哪些字段
可以从 Go SDK src/runtime/chan.go中找到:
3、channel如何保证线程安全的
通过hchan中的lock
4、有缓冲区与无缓冲区channel的区别
有缓冲区:即使在没有接受方的情况下,发送方也可以写入一定量的数据
无缓冲区:若没有接受方,则发送方那里会阻塞
5、channel的同步读写操作、异步读写操作、以及阻塞读写操作
同步读
sendq里面有数据,读取的时候我先从sendq里面拿到一个sg,然后将数据赋值给我当前这个协程,我就立马得到一个结果。
同步写
反过来针对recvq
异步读写
有可能没有接受者,但是我不管,我就向缓冲区里发送数据,至于你什么时候过来取一个数据,我不管。
阻塞读写
没有缓冲区,或者缓冲区已经满了,此时发送者不能发送数据了,直到有接收者接收,才会激活我们的send协程去发送数据。
6、channel关闭广播通知是如何实现的
channel的关闭广播通知机制是啥?
就是说当一个goroutine关闭一个channel时,其他正在通过
<-channel语法从该channel接收数据的goroutine会立即感知到这一点。如果它们正在阻塞等待channel中的数据,那么它们会立即从阻塞状态返回,并且接收操作的结果会是一个channel类型的零值,同时第二个返回值(一个布尔值,表示是否成功从channel接收到数据)会是false,表示channel已经关闭且没有更多的数据可以接收。
我们可以看一下源码:
func closechan(c *hchan) { if c == nil { panic(plainError("close of nil channel")) } lock(&c.lock) if c.closed != 0 { unlock(&c.lock) panic(plainError("close of closed channel")) } if raceenabled { callerpc := getcallerpc() racewritepc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(closechan)) racerelease(c.raceaddr()) } c.closed = 1 var glist gList // release all readers for { sg := c.recvq.dequeue() if sg == nil { break } if sg.elem != nil { typedmemclr(c.elemtype, sg.elem) sg.elem = nil } if sg.releasetime != 0 { sg.releasetime = cputicks() } gp := sg.g gp.param = unsafe.Pointer(sg) sg.success = false if raceenabled { raceacquireg(gp, c.raceaddr()) } glist.push(gp) } // release all writers (they will panic) for { sg := c.sendq.dequeue() if sg == nil { break } sg.elem = nil if sg.releasetime != 0 { sg.releasetime = cputicks() } gp := sg.g gp.param = unsafe.Pointer(sg) sg.success = false if raceenabled { raceacquireg(gp, c.raceaddr()) } glist.push(gp) } unlock(&c.lock) // Ready all Gs now that we've dropped the channel lock. for !glist.empty() { gp := glist.pop() gp.schedlink = 0 goready(gp, 3) } }
流程是这样的,大概干了这么几件事:
1、先锁住
2、改变状态,closed置为1
3、遍历所有的接收者队列里面的东西,然后将sg.ele给它赋空,将sg.g去push到一个glist里面
4、遍历所有发送者的等待队列,然后将sg.elem给它赋空,将sg.g去push到一个glist里面
5、解锁
6、遍历刚刚的glist,把所有等待者对应里面这些挂起的协程,都去给他激活。激活到接收协程的时候,接收协程会去执行我们的receive,就会接收到我们的零值。
