博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
golang 学习笔记——channel
阅读量:4147 次
发布时间:2019-05-25

本文共 3515 字,大约阅读时间需要 11 分钟。

Do not communicate by sharing memory; instead, share memory by communicating.

这种方式的优点是通过提供原子的通信原语,避免了竞态情形(race condition)下复杂的锁机制。

chan 源码实现

type hchan struct {	qcount   uint           // 已经接收但还没被取走的元素的个数,函数 len 返回这个字段的值;	dataqsiz uint           // 队列buffer的大小,cap函数可以返回这个字段的值以及队列buffer的指针,是一个定长的环形数组;	buf      unsafe.Pointer // 缓冲chan的循环队列的指针,非缓冲为自己的地址		elemsize uint16			// chan中元素的大小	closed   uint32			// 是否已close,未关闭=0	elemtype *_type 		// chan中元素类型	sendx    uint   		// send在buffer中的索引	recvx    uint   		// recv在buffer中的索引	recvq    waitq 	 		// receiver的等待队列	sendq    waitq  		// sender的等待队列 	lock mutex 			// 互斥锁}

在 channel 的内部实现中(具体定义在 $GOROOT/src/runtime/chan.go 里),维护了 3 个队列:

  • 读等待协程队列 recvq,维护了阻塞在读此 channel 的协程列表
  • 写等待协程队列 sendq,维护了阻塞在写此 channel 的协程列表
  • 缓冲数据队列 buf,用环形队列实现,不带缓冲的 channel 此队列 size 则为 0
    在这里插入图片描述

makechan

初始化 hchan 简单的分为三种情况:(为啥?)

switch {// no buffer 的场景,这种 channel 可以看成 pipecase mem == 0:    c = (*hchan)(mallocgc(hchanSize, nil, true))    c.buf = c.raceaddr()// 元素不含指针,分配一个连续大内存块;case elem.ptrdata == 0:    c = (*hchan)(mallocgc(hchanSize+mem, nil, true))    c.buf = add(unsafe.Pointer(c), hchanSize)// 元素含有指针,hchan 结构体和 buffer 内存块单独分配;default:    c = new(hchan)    c.buf = mallocgc(mem, elem, true)}

chansend

  • chan 入队(block=true): chansend1selectnbsend
  • 结合 select(block=false):selectnbrecvselectnbrecv2

chanrecv

  • chan 出队(block=true): chanrecv1chanrecv2
  • 结合 select(block=false):selectnbrecvselectnbrecv2
v := <-c// ok is false when ch is closed。非阻塞。v, ok := <-c //读取一个已关闭的 channel 时,总是能读取到对应类型的零值,为了和读取非空未关闭 channel 的行为区别,可以使用两个接收值

其他接口

  • 结合 select 语句 selectnbsend selectnbrecv selectnbrecv2 (非阻塞,失败为default分支)
  • 结合 for-range 语句 chanrecv2

当协程尝试从未关闭的 channel 中读取数据时,内部的操作如下:

  1. 当 buf 非空时,此时 recvq 必为空,buf 弹出一个元素给读协程,读协程获得数据后继续执行,此时若 sendq 非空,则从sendq 中弹出一个写协程转入 running 状态,待写数据入队列 buf ,此时读取操作 <- ch 未阻塞;
  2. 当 buf 为空但 sendq 非空时(不带缓冲的 channel),则从 sendq 中弹出一个写协程转入 running 状态,待写数据直接传递给读协程,读协程继续执行,此时读取操作 <- ch 未阻塞;
  3. 当 buf 为空并且 sendq 也为空时,读协程入队列 recvq 并转入 blocking 状态,当后续有其他协程往 channel 写数据时,读协程才会重新转入 running 状态,此时读取操作 <- ch 阻塞。

当协程尝试往未关闭的 channel 中写入数据时,内部的操作如下:

  1. 当队列 recvq 非空时,此时队列 buf 必为空,从 recvq 弹出一个读协程接收待写数据,此读协程此时结束阻塞并转 running 状态,写协程继续执行,此时写入操作 ch <- 未阻塞;
  2. 当队列 recvq 为空但 buf 未满时,此时 sendq 必为空,写协程的待写数据入 buf 然后继续执行,此时写入操作 ch<- 未阻塞;
  3. 当队列 recvq 为空并且 buf 为满时,此时写协程入队列 sendq 并转入 blokcing 状态,当后续有其他协程从channel 中读数据时,写协程才会重新转入 running 状态,此时写入操作 ch <- 阻塞。

关闭 non-nil channel 时,内部的操作如下:

  1. 当队列 recvq 非空时,此时 buf 必为空,recvq 中的所有协程都将收到对应类型的零值然后结束阻塞状态;
  2. 当队列 sendq 非空时,此时 buf 必为满,sendq 中的所有协程都会产生 panic ,在 buf 中数据仍然会保留直到被其他协程读取。

Q&A

  • 用channel实现以下应用场景:
    • futures/promises条件变量(condition variable)广播通知信号量互斥量
  • 以下场景如何优雅的关闭 channel (代码实现)
    • 一写多读
    • 多写一读
    • 多写多读

总结

  • channel 除在可以用来在协程之间通信外,其阻塞和唤醒协程的特性也可以用作协程之间的同步机制
  • 关闭不再需要使用的 channel 并不是必须的。跟其他资源比如打开的文件、socket 连接不一样,这类资源使用完后不关闭后会造成句柄泄露,channel 使用完后不关闭也没有关系,channel 没有被任何协程引用后最终会被 GC 回收。关闭 channel 一般是用来通知其他协程某个任务已经完成了。golang 也没有直接提供判断 channel 是否已经关闭的接口。所以使用的时候要特别注意,不要让协程阻塞在 channel 上,这种情况很难检测到,而且会造成 channel 和阻塞在 channel 的协程占有的资源无法被 GC 清理最终导致内存泄露。
  • golang 的 channel 是一个环形队列(ringbuffer)/ FIFO 的实现。称 chan 为管理结构,channel 里面可以放任何类型的对象,称之为元素。对队列的读写都是原子的操作,不需要加锁。对 channel 的操作行为结果总结如下:
操作 nil channel closed channel not-closed non-nil channel
close panic panic 成功 close
写 ch <- 一直阻塞 panic 阻塞或成功写入数据
读 <- ch 一直阻塞 读取对应类型零值 阻塞或成功读取数据
  • 读/写类型是值类型的 channel 时,如果元素 size 比较大时,应该使用指针代替,避免频繁的内存拷贝开销。

参考资料

http://www.usingcsp.com/

https://go101.org/article/channel.html
https://go101.org/article/channel-use-cases.html
https://go101.org/article/channel-closing.html
https://www.jianshu.com/p/819aa9b9af86

汇编

cat <

转载地址:http://xviti.baihongyu.com/

你可能感兴趣的文章
idea添加gradle模块报错The project is already registered
查看>>
在C++中如何实现模板函数的外部调用
查看>>
在C++中,关键字explicit有什么作用
查看>>
C++中异常的处理方法以及使用了哪些关键字
查看>>
如何定义和实现一个类的成员函数为回调函数
查看>>
内存分配的形式有哪些? C++
查看>>
什么是内存泄露,如何避免内存泄露 C++
查看>>
栈和堆的空间大小 C++
查看>>
什么是缓冲区溢出 C++
查看>>
sizeof C++
查看>>
使用指针有哪些好处? C++
查看>>
引用还是指针?
查看>>
checkio-non unique elements
查看>>
checkio-medium
查看>>
checkio-house password
查看>>
checkio-moore neighbourhood
查看>>
checkio-the most wanted letter
查看>>
CyclicBarrier使用及源码分析
查看>>
CountDownLatch使用及源码分析
查看>>
java并发包中Semaphore使用及源码分析
查看>>