golang runtime源码阅读 channal实现

概念及使用场景通道(channal)是Golang实现CSP并发模型的关键,分为 有缓冲通道 和无缓冲通道。有缓冲管道: channal持有一个固定大小的队列,队列满时发送者将阻塞(反之亦然)。多用于数据共享。无缓冲管道: 发送和接收数据同时完成,如果没有goroutine读取channal,则发送者阻塞(反之亦然)。多用于协程同步。有缓冲+select: +for实现对多个chan的监...
摘要由CSDN通过智能技术生成

概念及使用场景

通道(channal)是Golang实现CSP并发模型的关键,分为 有缓冲通道 和无缓冲通道。
  • 有缓冲管道: channal持有一个固定大小的队列,队列满时发送者将阻塞(反之亦然)。多用于数据共享。
  • 无缓冲管道: 发送和接收数据同时完成,如果没有goroutine读取channal,则发送者阻塞(反之亦然)。多用于协程同步。
  • 有缓冲+select: +for实现对多个chan的监听操作 +time实现超时控制

基本使用

简单的 无缓冲channal 使用

func main() {
   
 ch := make(chan int)  //创建无缓冲管道
 
 go func() {
             //发起goroutine,向管道写入数据
 	ch <- 1
 	close(ch)                    //关闭管道
 }() 

 t := <- ch                     //主goroutine等待读取到管道数据后退出
 fmt.Println(t)
 return

需要注意的是:
1. 无缓冲在写入时,必须有其他线程在对端接受,否则线程将阻塞。
2. 向关闭的chan写入数据将Panic
3. 可以从关闭的chan中读取数据
4. 重复关闭chann会导致panic

基本结构及内存布局

chan结构体定义在 ./src/runtime/chan.go文件,type hchan struct

type hchan struct {
   
	qcount   uint           // 队列数据长度len
	dataqsiz uint           // 缓冲区长度cap c:=make(chan int, 10)即此值为10
	buf      unsafe.Pointer // 指向dataqsiz数组的指针
	elemsize uint16   		//元素类型大小 sizeof(struct)
	closed   uint32  		//关闭标志
	elemtype *_type 		// chan接收的元素类型
	sendx    uint   		// 写入chan索引,写入1个数据时,sendx+1
	recvx    uint   		// 读取chan索引,被读走1个数据时,recvx+1
	recvq    waitq  		// 阻塞在chann上的读等待队列
	sendq    waitq  		// 阻塞在chann上的写等待队列
	lock 	 mutex  		//互斥锁 保证hchan的数据读写安全,包括被阻塞的waitq中的sudog
}

waitq类型链表,sudog 为封装的goroutine,包含等待 读/写的被阻塞的goroutine信息。

type waitq struct {
   
	first *sudog
	last  *sudog
}

type sudog struct {
   
	g *g  					// 阻塞的goroutine
	isSelect bool  			// select中使用chan是特殊处理的,本章暂时不论
	next     *sudog		
	prev     *sudog
	elem     unsafe.Pointer // 数据指针(指向堆内存或栈空间)
	acquiretime int64
	releasetime int64
	ticket      uint32
	parent      *sudog // semaRoot binary tree
	waitlink    *sudog // g.waiting list or semaRoot
	waittail    *sudog // semaRoot
	c           *hchan // channel
}
结构中大约了解下 channal 的实现
  1. channal 有缓存时通过读写索引读取数据,并保存了可容纳元素数量(dataqsize) 及当前元素量(qcount)。recvq和sendq为两个队列,遵循先进先出原则,队列元素sudog为封装后的goroutine,包含goroutine等待的chan信息和数据地址,一般为阻塞状态。互斥锁保证hchan中的数据读写安全。

  2. 对于无缓冲channal,recvq 和 sendq至少有一个为空,且 dataqsiz 和 qcount 都为0。

初始化 channal 过程如下
// use example:
ch := make(chan int, 10)
// code
func makechan(t *chantype, size int) *hchan {
   
	elem := t.elem
	// 检查是否存在size越界问题	
	if size < 0 || uintptr(size) > maxSliceCap(elem.size) || uintptr(size)*elem.size > maxAlloc-hchanSize {
   
		panic(plainError("makechan: size out of range"))
	}
	var c *hchan
	switch {
   
	// make(chan int, 0) size=0 but elem.size=8
	// make(struct{}, 2) size=2 but elem.size=0
	case size == 0 || elem.size == 0:  
		//分配hchan大小,无缓冲chan只分配这些,以下是有缓冲chan
		c = (*hchan)(mallocgc(hchanSize, nil, true))
		// race检测,不懂
		c.buf = c.raceaddr()
	// channal类型不包含指针 
	case elem.kind&kindNoPointers != 0:
		// 分配空间大小为(hchan结构大小 单个元素大小*请求分配数量)的堆内存
		c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true))
		// c.buf指针指向hchan结构后,即存储元素区
		c.buf = add(unsafe.Pointer(c), hchanSize)
	// channal类型包含指针
	default:
		// 分配一块hchan内存
		
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值