golang channel实现

本文探讨了Golang中channel的实现原理,强调通过通信共享数据的概念。内容涉及channel的内部结构hchan,包括循环队列buf、sendq和recvq列表,以及在发送和接收数据时如何使用mutex保证安全性。当channel满时,发送操作会导致goroutine进入等待状态并加入sendq;同样,当尝试从空channel读取时,goroutine会加入recvq。通过这种方式,Golang实现了goroutine之间的同步和数据交换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

golang 在多协程之间交互,除了支持传统的通过共享数据来进行通信(公共数据加锁),更推崇通过通信来共享数据。所谓的通过通信来共享数据,借鉴于csp并发模型(两个独立的并发实体通过共享的通讯管道进行通信的并发模型),即通过共享的channel在多个goroutine之间传递数据,达到共享数据的需求。

这里主要谈谈channel是如何实现的,先看看源码:

type hchan struct {
	qcount   uint           // 队列中的元素个数
	dataqsiz uint           // 循环队列的数据大小
	buf      unsafe.Pointer // 缓存数组指针
	elemsize uint16
	closed   uint32
	elemtype *_type // element type
	sendx    uint   // 待发送的index
	recvx    uint   // 待接收的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
}

注意,make(chan string),返回的是一个hchan的指针,所以在多个goroutine之间共享的是一个地址。

hchan结构体使用一个循环队列来保存groutine之间传递的数据,使用两个list来保存往该chan发送和从该chan接收数据的goroutine,还有一个mutex来保证操作这些结构的安全。

初始的时候hchan结构体的buf为空,sendx和recvx都为0,当向chan里发送数据的时候,会首先对buf加锁,然后将要发送的数据copy到buf,并增加sendx的值,最后释放buf的锁。然后读取chan的时候首先对buf加锁,然后将buf里的数据copy到待接收的变量里,增加recvx,最后释放锁。整个过程,多个goroutine之间没有共享的内存,底层通过hchan结构体的buf,使用copy内存的方式进行通信,最后达到了共享内存的目的。

在hchan中的recvq,sendq又是做什么用的呢,recvq是由 recv 行为(也就是 <-ch)阻塞在 channel 上的 goroutine 队列,sendq是由 send 行为 (也就是 ch<-) 阻塞在 channel 上的 goroutine 队列。 这个队列(waitq)里面存的是sudog结构,sudog其实就是一个对 g 的结构的封装。

当向chan写数据的时候发生阻塞的时候又发生了什么呢?
当向已经满了的ch发送数据的时候,runtine检测到对应的hchan的buf已经满了,会通知调度器,调度器会将goroutine的状态设置为waiting, 移除与线程M的联系,然后从P的runqueue中选择一个goroutine在线程M中执行,此时这个goroutine就是阻塞着的。d当这个goroutine变为waiting状态后,会创建一个代表自己的sudog的结构,然后放到sendq这个list中,sudog结构中保存channel相关的变量的指针(如果该Goroutine是sender,那么保存的是待发送数据的变量的地址,如果是receiver则为接收数据的变量的地址,之所以是地址,前面我们提到在传输数据的时候使用的是copy的方式),当另一个goroutine从ch中接收一个数据时,会通知调度器,设置先前阻塞的goroutine的状态为runnable,然后将加入P的runqueue里,等待线程执行。

当向chan读数据的时候发生阻塞的时候又发生了什么呢?
和前面介绍的写阻塞一样,读阻塞也会创建一个sudog结构体,保存接收数据的变量的地址,该sudog结构体是放到了recvq列表里,当其他goroutine向ch发送数据的时候,runtime并没有对hchan结构体题的buf进行加锁,而是直接将要发送到ch的数据copy到了读阻塞sudog里对应的elem指向的内存地址!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值