无缓冲channel的复制机制
文章目录
在Go语言中,无缓冲channel通过同步发送和接收操作,实现了数据直接从发送者的内存复制到接收者的内存空间。具体实现机制如下:
1. 同步机制与等待队列
- 无缓冲channel的特性:发送和接收操作必须同时就绪(即“同步”)。若一方未就绪,另一方会被阻塞并加入对应的等待队列。
- hchan结构体:底层使用
hchan结构体管理channel,包含两个等待队列(sendq和recvq),分别存储阻塞的发送者和接收者的sudog(goroutine的封装结构)。
2. 数据直接复制的核心流程
-
发送者就绪时的操作:
- 当发送者尝试发送数据时,若接收队列
recvq不为空,直接从队列中取出一个等待的接收者。 - 将发送者的数据直接复制到接收者提供的内存地址(通过
typedmemmove函数)。 - 唤醒接收者的goroutine。
- 当发送者尝试发送数据时,若接收队列
-
接收者就绪时的操作:
- 当接收者尝试接收数据时,若发送队列
sendq不为空,取出一个等待的发送者。 - 从发送者的数据源地址直接复制到接收者的目标地址。
- 唤醒发送者的goroutine。
- 当接收者尝试接收数据时,若发送队列
3. 关键代码逻辑
在Go的运行时源码(runtime/chan.go)中:
-
发送操作(
chansend函数):- 若存在等待的接收者,调用
send函数直接将数据拷贝到接收者的内存。
1 2 3 4if sg := c.recvq.dequeue(); sg != nil { send(c, sg, ep, func() { unlock(&c.lock) }, 3) return true } - 若存在等待的接收者,调用
-
接收操作(
chanrecv函数):- 若存在等待的发送者,调用
recv函数从发送者拷贝数据。
1 2 3 4if sg := c.sendq.dequeue(); sg != nil { recv(c, sg, ep, func() { unlock(&c.lock) }, 3) return true, true } - 若存在等待的发送者,调用
-
数据拷贝(
typedmemmove函数):- 通过类型信息和目标地址,直接执行内存复制。
1 2 3 4 5 6 7 8func typedmemmove(typ *_type, dst, src unsafe.Pointer) { if typ.ptrdata == 0 { memmove(dst, src, typ.size) } else { bulkBarrierPreWrite(uintptr(dst), uintptr(src), typ.size) memmove(dst, src, typ.size) } }
4. 优势与性能
- 无中间缓冲区:数据直接从发送者到接收者,减少一次内存拷贝(相比有缓冲channel需先写入缓冲区再读出)。
- 避免锁竞争:同步操作通过等待队列和原子操作管理,减少锁的粒度。
5. 示例场景
假设两个goroutine(G1发送,G2接收):
- G1先执行发送:若G2未就绪,G1被阻塞并加入
sendq。 - G2执行接收:发现
sendq中有G1,直接触发内存复制。 - 数据复制:G1的变量数据通过
memmove复制到G2的变量地址。 - 双方唤醒:G1和G2继续执行后续代码。
总结
无缓冲channel通过同步等待队列机制,在发送者和接收者均就绪时,直接通过内存复制(memmove)传递数据。这一设计避免了额外的缓冲区开销,保证了高效的goroutine间通信。