在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 的其余字段进行初始化,包括元素类型大小、元素类型、容量以及锁的初始化.