Go 语言中的 Channel 相关问题整理
# Channel
Go 语言中,不通过共享内存实现通信,而通过通信来实现共享内存
Go 语言的 CSP(通信顺序进程)并发模型,是通过 goroutine 和 channel 实现的
channel 遵守 FIFO 原则,分为有缓冲区和无缓冲区
假设 ch 是函数栈上的一个指针,其指向堆中的 hchan 数据结构
- hchan 支持多个协程的并发访问,所以包含一把互斥锁
- 包含缓冲区指针(缓冲区是一个数组)
- 包含已经存储了多少数据
- 包含最多能存储多少数据
- 包含每个元素占多大空间
- 因为内存复制、gc 等机制依赖于数据类型,所以包含一个指针指向元素类型的类型元数据
- 包含发送、接受队列和发送、接收下标
- 包含关闭状态
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 // 互斥锁,支持多协程访问 }
缓冲区是一个环形缓冲区 Ring Buffer
发送数据时,
- 不发生阻塞的情况:缓冲区非空且有空闲区域、缓冲区为空且有协程等待接收数据
- 会发生阻塞的情况:channel 为 nil、缓冲区为空且没有协程等待接收数据、缓冲区满了且没有协程等待接收数据
- 可以使用 select + default 的非阻塞式写法
select { case ch<- 10: // ... default: // ... }
接收数据时,
- 不发生阻塞的情况:缓冲区非空有数据、缓冲区为空且有协程等待发送数据
- 会发生阻塞的情况:channel 为 nil、缓冲区为空且没有协程等待发送数据、有缓冲区但没有数据且没有协程等待发送数据
- 也可以使用 select + default 的非阻塞写法
多路 select 中,每个 case 可以是一个 channel 的发送 或 接受操作,其中 default 可选
select {
case a = <-ch1:
// ...
case ch2 <- b
}
- 用 channel 共享内存可以解耦生产者和消费者,但容易引发阻塞甚至死锁
Last Updated: 9/6/2023, 4:49:24 PM