Go 数据类型 | channel 进阶(上)

channel(通道)用于 goroutine(协程)之间的通信。它提供了一种在不同协程之间传递数据的机制。channel 是一种类型安全的、阻塞的、先进先出(FIFO)的数据结构,确保发送的数据按照发送的顺序接收。

Go 语言提倡通过通信来共享内存,而不是通过共享内存来通信,CSP(Communicating Sequential Process)并发模型,就是通过 goroutine 和 channel 来实现的。

1.底层实现

1.1 hchan

通过 var 声明或者 make 函数创建的 channel 变量是一个存储在函数栈帧上的指针,指向堆上的 hchan 结构体。

// src/runtime/chan.go

type hchan struct {
   
    qcount   uint     // 循环数组中的元素数量
    dataqsiz uint     // 循环数组的长度
    // channel 分为无缓冲和有缓冲两种。
    // 对于有缓冲的 channel 存储数据,使用了 ring buffer(环形缓冲区)来缓存写入的数据,本质是循环数组。
    // 为什么是循环数组?普通数组不行吗?普通数组容量固定,更适合指定的空间,弹出元素时,普通数组需要全部都前移。
    buf      unsafe.Pointer // 指向底层循环数组的指针(环形缓冲区)
    elemsize uint16   // 元素的大小
    closed   uint32   // channel是否关闭的标志
    elemtype *_type   // channel中的元素类型
    // 当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置
    sendx    uint           // 下一次写下标的位置
    recvx    uint           // 下一次读下标的位置    
    // 尝试读/写 channel 被阻塞的 goroutine
    recvq    waitq  // 读等待队列
    sendq    waitq  // 写等待队列

    lock mutex //互斥锁,保证读写 channel 时不存在并发竞争问题
}

image

1.2 sudog

等待队列是双向链表结构,每个节点是一个 sudog 结构体变量,记录哪个协程在等待,等待的是哪个 channel,等待发送/接收的数据在哪里。

// $GOROOT/src/runtime/chan.go

type waitq struct {
   
    first *sudog
    last  *sudog
}

type sudog struct {
   
    g *g

    next *sudog
    prev *sudog
    elem unsafe.Pointer 
    
    ...

    c *hchan 
}

2.执行基本操作时的底层操作

2.1 创建

当我们使用 make(chan T, cap) 来创建 channel 时,make 语法会在编译时转换为 makechan64makechan

// $GOROOT/src/runtime/chan.go

func makechan64(t *chantype, size int64) *hchan {
   
    // 确保将 size 转换为 int 类型后与原始值相等,否则引发 panic
    if int64(int(size)) != size {
   
        panic(plainError("makechan: size out of range"))
    }

    return makechan(t, int(size))
}

func makechan(t *chantype, size int) *hchan {
   
    // 获取元素类型
    elem := t.elem

    // 编译器已经检查了这一点,但是为了安全起见再次进行检查
    // 如果元素大小大于等于 1<<16(65536),则抛出错误
    if elem.size >= 1<<16 {
   
        throw("makechan: invalid channel element type")
    }
    // 检查 hchanSize 是否能被 maxAlign 整除,或者 elem.align 是否大于 maxAlign
    if hchanSize%maxAlign != 0 || elem.align > maxAlign {
   
        throw("makechan: bad alignment")
    }

    // 计算所需的内存大小
	mem, overflow := math.MulUintptr(elem.size, uintptr(size))
    // 检查是否溢出、所需内存是否超过 maxAlloc - hchanSize 或者 size 是否小于 0
	if overflow || mem > maxAlloc-hchanSize || size < 0 {
   
		panic(plainError("makechan: size out of range"))
	}

	// 创建 hchan 指针
	var c *hchan
	switch {
   
	case mem == 0:
		// 队列或元素大小为 0:分配 hchan 的内存
		c = (*hchan)(mallocgc(hchanSize, nil, true))
		// 设置 buf 用于与竞争检测器同步
		c.buf = c.raceaddr()
	case elem.ptrdata == 0:
		// 元素不包含指针
        // 一次性分配 hchan 和 buf 的内存
		c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
		c.buf = add(unsafe.Pointer(c), hchanSize)
	default:
		// 元素包含指针:创建 hchan,然后分配 buf 的内存
		c = new(hchan)
		c.buf = mallocgc(mem, elem, true)
	}

    // 设置 hchan 的属性
	c.elemsize = uint16(elem.size)
	c.elemtype = elem
	c.dataqsiz 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值