Channel 源码解读

在go中对于异步编程场景之间不同的goroutine之间对对方的掌控力度其实较弱(相较于同步压栈)通过线程的一个通信机制–chan


前言

基于chan来噜一道题吧

基于chan实现一个map
1. 面相高并发
2. 只存在插入和查询,时间复杂度O(1)
3. 查询时,若key存在,直接返回val,如key不存在,阻塞直到key,val存放进chan,返回val;若指定时间没返回,返回超时报错
4. 写出真实代码,不存在死锁和panic

一、 chan基本知识

  • 因为chan属于runtime包下面的,所以需要make关键字进行声明chan
  • ch := make(chan int) 无缓冲chan (长度为0的通道)
  • ch := make(chan int, 10) 有缓冲通道 (即使没人读,前10次写入不会阻塞)如果满了,再继续写入,会进入阻塞
  • 读取操作: val <- ch; <-ch ; val, ok <- ch
  • 对于关闭的chan进行读取,并不会panic,会返回类型零值
  • 写操作: ch <- data (chan里面必须有空间,如果没有空间会被阻塞)
  • chan 关闭 close(ch);
  • 如果被关闭的chan,再进行读chan,无论chan中是否存在数据,进行读都不会被阻塞;如果chan里面存在数据,则会读出来对应数据,若是空的,则会读出来对应chan类型的零值
  • 如果往已经关闭的chan去写数据,则会panic
  • select多路复用

二、核心数据结构

1.1 hchan

因为chan要实现有缓冲(环形数组长度->make时指定的容量);实现阻塞(runtime包下面调用gopark()方法;锁);因接受和发送陷入的阻塞携程队列(gmp里面讲过封装g结构的环形队列)

type hchan struct {
    qcount   uint           // 当前 channel 中存在多少个元素;
    dataqsiz uint           // 当前 channel 能存放的元素容量;
    buf      unsafe.Pointer // 中用于存放元素的环形缓冲区
    elemsize uint16   //元素类型的大小;
    closed   uint32	//标识 channel 是否关闭;
    elemtype *_type // channel 元素类型;
    sendx    uint   // 发送元素进入环形缓冲区的 index;
    recvx    uint   // 接收元素所处的环形缓冲区的 index;
    recvq    waitq  // 因接收而陷入阻塞的协程队列;
    sendq    waitq  // 因发送而陷入阻塞的协程队列;
    
    lock mutex
}

1.2 waitq

type waitq struct {
    first *sudog //队列头部
    last  *sudog  //队列尾部
}

waitq:阻塞的协程队列

1.3 sudog

type sudog struct {
    g *g	//goroutine,协程;

    next *sudog	//队列中的下一个节点;
    prev *sudog	//队列中的前一个节点;
    elem unsafe.Pointer // 读取/写入 channel 的数据的容器;

    isSelect bool	// 标识当前协程是否处在 select 多路复用的流程中;

    c        *hchan //标识与当前 sudog 交互的 chan.
}

三、构造器函数

func makechan(t *chantype, size int) *hchan {
    elem := t.elem

    // ...
    mem, overflow := math.MulUintptr(elem.size, uintptr(size))
    if overflow || mem > maxAlloc-hchanSize || size < 0 {
        panic(plainError("makechan: size out of range"))
    }

    var c *hchan
    switch {
    case mem == 0:
        // Queue or element size is zero.
        c = (*hchan)(mallocgc(hchanSize, nil, true))
        // Race detector uses this location for synchronization.
        c.buf = c.raceaddr()
    case elem.ptrdata == 0:
        // Elements do not contain pointers.
        // Allocate hchan and buf in one call.
        c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
        c.buf = add(unsafe.Pointer(c), hchanSize)
    default:
        // Elements contain pointers.
        c = new(hchan)
        c.buf = mallocgc(mem, elem, true)
    }

    c.elemsize = uint16(elem.size)
    c.elemtype = elem
    c.dataqsiz = uint(size)
    
    lockInit(&c.lock, lockRankHchan)

    return
}
  • 判断申请内存空间大小是否越界,mem 大小为 element 类型大小与 element 个数相乘后得到,仅当无缓冲型 channel 时,因个数为 0 导致大小为 0;
  • 根据类型,初始 channel,分为 无缓冲型、有缓冲元素为 struct 型、有缓冲元素为 pointer 型 channel;
  • 倘若为无缓冲型,则仅申请一个大小为默认值 96 的空间;
  • 如若有缓冲的 struct 型,则一次性分配好 96 + mem 大小的空间,并且调整 chan 的 buf 指向 mem 的起始位置;
  • 倘若为有缓冲的 pointer 型,则分别申请 chan 和 buf 的空间,两者无需连续;
  • 对 channel 的其余字段进行初始化,包括元素类型大小、元素类型、容量以及锁的初始化.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值