ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] ### **channel 是否线程安全?锁用在什么地方?** 是线程安全的,channel的底层是一个hchan结构,里面加了mutex ~~~ type hchan struct { ...... lock mutex //互斥锁,chan不允许并发读写 } ~~~ ### **go channel 的底层实现原理** ~~~ type hchan struct { qcount uint dataqsiz uint buf unsafe.Pointer elemsize uint16 closed uint32 elemtype *_type sendx uint recvx uint recvq waitq sendq waitq lock mutex //互斥锁,chan不允许并发读写 } ~~~ qcount:channel里面的元素计数。内建函数 len 可以返回这个字段的值。已接收还没被取走 dataqsiz:环形队列大小,即可存放元素的个数。make(chan int,10),10就是这个值 buf:当 channel 设置了缓冲数量时,该 buf 指向一个存储缓冲数据的区域,该区域是一个循环队列的数据结构 elemsize :要发送或接收的数据类型大小 closed :标识关闭状态 elemtype :元素类型 sendx :当 channel 设置了缓冲数量时,数据区域即循环队列此时已发送数据的索引位置 recvx:当 channel 设置了缓冲数量时,数据区域即循环队列此时已接收数据的索引位置 recvq :想读取数据但又被阻塞住的 goroutine 队列,即:等待读消息的goroutine队列 sendq :想发送数据但又被阻塞住的 goroutine 队列,即:等待写消息的goroutine队列 ### **channel的特点** 2种类型:有缓冲、无缓冲 3种模式:双向通道,只允许发送通道、只允许接收通道 3种状态:未初始化(nil)、关闭、正常 | | nil | 关闭 | 正常 | | --- | --- | --- | --- | | 关闭 | panic | panic | 可以关闭 | | 发送 | 阻塞导致死锁 | panic | 成功发送/阻塞 | | 接收 | 阻塞导致死锁 | 缓冲区有值,读值,没有值,返回零值 | 成功接收/阻塞 | ### **向 channel 发送数据和从 channel 读数据的流程是什么样的?** **向 channel 写数据:** 若等待接收队列 recvq 不为空,无论缓冲区中无数据或无缓冲区,将直接从 recvq 取出 G ,并把数据写入,最后把该 G 唤醒,结束发送过程。 若缓冲区中有空余位置,则将数据写入缓冲区,结束发送过程。 若缓冲区中没有空余位置,则将发送数据写入 G,将当前 G 加入 sendq ,进入睡眠,等待被读 goroutine 唤醒。 **从 channel 读数据** 若等待发送队列 sendq 不为空,且没有缓冲区,直接从 sendq 中取出 G ,把 G 中数据读出,最后把 G 唤醒,结束读取过程。 如果等待发送队列 sendq 不为空,缓冲区已满,从缓冲区中首部读出数据,把 G 中数据写入缓冲区尾部,把 G 唤醒,结束读取过程。 如果缓冲区中有数据,则从缓冲区取出数据,结束读取过程。 如果缓冲区中没有数据,将当前 goroutine 加入 recvq ,进入睡眠,等待被写 goroutine 唤醒。 **关闭 channel** 1.关闭 channel 时会将 recvq 中的 G 全部唤醒,本该写入 G 的数据位置为 nil。将 sendq 中的 G 全部唤醒,但是这些 G 会 panic。 ### **chan 使用场景** 消息传递、请求、响应转发,任务分发,限流,同步与异步