1、数据结构
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
timer *timer // timer feeding this chan
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
type waitq struct {
first *sudog
last *sudog
}
2、底层实现
数据结构
channel 用于 goroutine 之间通信和同步。主要由一个环形缓冲区(对于带缓冲的 channel)和两个指针(读指针和写指针)组成。每个 channel
还有一个锁(通常是自旋锁)来保证并发安全。
发送与接收
发送与接收是相对于协程而言的。
- 发送操作(
chan <- value
)会检查channel
是否已满:- 如果是无缓冲的
channel
,发送会阻塞直到有接收操作。 - 如果是有缓冲的
channel
,发送会阻塞直到有空间可用。
- 如果是无缓冲的
- 接收操作(
value := <-chan
)会检查channel
是否为空:- 如果是无缓冲的
channel
,接收会阻塞直到有发送操作。 - 如果是有缓冲的
channel
,接收会阻塞直到有数据可读
- 如果是无缓冲的
发送队列(sendq)和接收队列(recvq)
-
recvq 队列:当一个 goroutine 执行接收操作时,Go 调度器会检查 channel 的状态,接收的 goroutine 会被挂起,并加入到
recvq
队列。 -
sendq 队列:当一个 goroutine 执行发送被阻塞时,发送的 goroutine 会被挂起,并加入到
sendq
队列。
发送和接收队列是FIFO队列,阻塞线程按先进先出顺序被调度,活跃线程优先于阻塞队列中的线程被调度。
调度
调度器会负责管理协程的状态,协程被channel阻塞时进入等待队列,此时调度器可以将其他可运行的 goroutine 调度到 CPU 上。
内存管理:
channel
的实现还涉及到内存分配和垃圾回收,确保在 goroutine 结束时正确释放相关资源。
3、问题
(1)G阻塞和M阻塞的区别?
G 被 channel、mutex、select 等阻塞时,G会被调度器放入G的等待队列,为等待状态,被唤醒后重新进入P的本地队列中被执行。(在全局队列和P的本地队列之外,还有一个goroutine的等待队列)
M 被I/O阻塞或系统调用时阻塞,会触发hand off机制使M与P分离,由其他M接管P,避免整个P饥饿。