Channel底层原理
🤧

Channel底层原理

Tags
Golang
底层原理
channel
闲来无事
Published
October 16, 2024
Author
chaochao
Language
Source

Golang channel 底层实现

貌似是我开始学到到go语言channel时,第一句映入我眼帘的话,也是我目前印象很深刻的一句话——“我们不要通过共享内存来实现通讯,而是通过通讯来共享内存”
 

1、channel应用场景

1)输出数据给其它使用方(协程间通信)

2)组合多个逻辑(channel+select 实现组合逻辑)

2、channel的重要的数据结构

可以看一下源码,channel底层数据结构的结构体有哪些字段
可以从 Go SDK src/runtime/chan.go中找到:
notion image
notion image
notion image
 
notion image

3、channel如何保证线程安全的

通过hchan中的lock

4、有缓冲区与无缓冲区channel的区别

有缓冲区:即使在没有接受方的情况下,发送方也可以写入一定量的数据
notion image
无缓冲区:若没有接受方,则发送方那里会阻塞
notion image

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,就会接收到我们的零值。