计算机系统应用教程网站

网站首页 > 技术文章 正文

go channel理解和解析,关于阻塞和解除阻塞

btikc 2024-10-25 10:50:21 技术文章 6 ℃ 0 评论

网上对于channel的解析有很多,我就分享一下个人理解。感觉channel类似 linux 里面管道,IO流的概念。它有输入和输出的概念,同时会发生阻塞。

无缓存的channel类型下面的图片,输入和输出直接经过channel。

func main() {
	ch := make(chan struct{}) // 无缓冲区的channel
	go func() {
		ch <- struct{}{} // 协程 阻塞挂起 直到有接收方
	}()

	for {
		fmt.Println(runtime.NumGoroutine())
		time.Sleep(time.Second)
	}
}

有缓冲区的channel,类似下面的这个图,拥有容量有线的环形元素数组和长度不限制的存放阻塞数据的队列。

有几个需要注意的点

  1. channel没有流出,也就是接收方。会阻塞挂起,(在执行)
  2. 拥有缓冲区的channel,缓冲区可以接收数据,不超容量不会阻塞

我们可以从channel的数据结构体上印证一下

type hchan struct {
    qcount   uint           // 当前 channel 中存在多少个元素;
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // channel 中用于存放元素的环形缓冲区;
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // 因接收而陷入阻塞的协程队列;
    sendq    waitq  // 因发送而陷入阻塞的协程队列;
    
    lock mutex // 锁
}

关于阻塞

发送和接收channel超出容量,都会陷入阻塞。看一下怎么发送的阻塞。主要就是 gopark的调用。

gopark 将 g 变为阻塞态 waiting (g与m解绑, g等待新一轮的调度)

// No stack splits between assigning elem and enqueuing mysg
	// on gp.waiting where copystack can find it.
	mysg.elem = ep
	mysg.waitlink = nil
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.waiting = mysg
	gp.param = nil
	c.sendq.enqueue(mysg)
	// Signal to anyone trying to shrink our stack that we're about
	// to park on a channel. The window between when this G's status
	// changes and when we set gp.activeStackChans is not safe for
	// stack shrinking.
	atomic.Store8(&gp.parkingOnChan, 1)
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
	// Ensure the value being sent is kept alive until the
	// receiver copies it out. The sudog has a pointer to the
	// stack object, but sudogs aren't considered as roots of the
	// stack tracer.
	KeepAlive(ep)

	// someone woke us up.
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	closed := !mysg.success
	gp.param = nil
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}

什么时候再变成可调度,我们看一下源码的 send channel 位置。会有调用 goready,将g由waiting阻塞态变为可调度,等待被调度。走gmp调度处理。

select为什么能监听channel

如果go协程已经陷入阻塞状态了,那么select为什么还能监听到channel呢?如果有select监听的时候,会进入一个类似自旋的状态,没有真正的阻塞。非阻塞模式。

默认情况下,读/写 channel 都是阻塞模式,只有在 select 语句组成的多路复用分支中,与 channel 的交互会变成非阻塞模式:

以上就是我关于channel的理解。文字描述有限,后面会有一次视频的描述。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表