go channel实现
来源:互联网 发布:东华大学网络 编辑:程序博客网 时间:2024/04/28 11:54
G语言经过多年的发展,于最近推出了第一个稳定版本。相对于C/C++来说,Go有很多独特之出,比如提供了相当抽象的工具,如channel和goroutine。本文主要介绍channel的实现方式。
简介
channel有四个操作:
- 创建:
c = make(chan int)
- 发送:
c <- 1
- 提取:
i <- c
- 关闭:
close(c)
根据创建方式的不同,channel还可分为有buffer的channel和没有buffer的channel。buffer的大小由make的第二个参数指定,默认为0,即没有buffer。创建有buffer的channel的方式是:c = make(chan int, 10)
channel的实现主要在文件src/pkg/runtime/chan.c
里面。它的数据结构如下:
structHchan{uint32qcount;// total data in the quint32dataqsiz;// size of the circular quint16elemsize;boolclosed;uint8elemalign;Alg*elemalg;// interface for element typeuint32sendx;// send indexuint32recvx;// receive indexWaitQrecvq;// list of recv waitersWaitQsendq;// list of send waitersLock;};
发送流程
Hchan
中的两个WaitQ(
recvq
和sendq)
是两个队列,分别保存等待从该channel提取和发送的goroutine。以向没有buffer的channel发送为例,
- 如果向该channel发送数据的goroutine发现
recvq
不为空,则从recvq
中取出一个goroutine,然后把数据传给它,发送完成,发送方goroutine可以继续执行。提取方goroutine则结束block状态,可以被调度执行。 - 否则,发送方goroutine被存入
sendq
队列,且发送方goroutine进入block状态,调度算法选择其它goroutine执行。
如果channel有buffer,
- 如果buffer里有空间,则把数据存入buffer,发送完成;如果
recvq
队列里有等待的goroutine,则取出一个,并将其唤醒,等待调度执行。发送方goroutine继续执行。 - 如果buffer已满,则发送方goroutine被存入
sendq
队列,发送方goroutine进入block状态,调度算法选择其它goroutine执行。
如果向已经关闭的channel发送数据,程序会报错并异常退出。如下面的程序:
package mainfunc main() {c := make(chan int)d := make(chan int)go func() {<-dclose(c)} ()d <- 4c <- 3}
从已经关闭的channel收取数据不会报错,也不会异常退出,但是我不确定得到什么样的值。除此之外,提取和发送的实现基本是相对的,就不再介绍了。
Buffer空间
buffer的空间紧挨着channel,是在创建的channel的时候一起分配的,
c = (Hchan*)runtime·mal(n + hint*elem->size);
其中hint
即为buffer的元素个数,会保存在dataqsiz
里,另外一起管理buffer的还有qcount
、sendx
和recvx
,分别表示buffer里的元素个数,下一次发送操作存放数据的位置,以及下一次提取数据的位置。这个buffer是个circular buffer。
Channel与Select
channel配合select语句,可以实现multiplex的效果,如:
select {case <-c1:case <-c2:}
c1
和c2
哪个channel先有数据到达,哪个case先执行;都没有数据,就block住;都有数据,以一个公平的方式随机选择一个case执行。select语句本身没有增加channel的操作方式,但是它本身的实现也很有趣:
- 当select被block住,它所在的goroutine将被挂在多个channel的
sendq
或者recvq
上。比如上面的例子中,select所在的goroutine将被挂在c1
和c2
的recvq
上,如果这时有另外两个goroutine同时分别向c1
和c2
发送数据,那么它们将操作同一个goroutine(尽管是不同的channel),这种情况下,要么加锁,要么用原子操作。这就是为什么dequeue
里要使用runtime·cas
的原因,虽然调用dequeue
之前上锁了,但那是给sendq
/recvq
上锁,不是给goroutine上锁。 - 不同goroutine里面的select语句可能操作同一组channel,那么就有上锁的必要。Go的实现里每个channel有自己的锁,所以select就需要上多个锁,稍有不慎,可能导致死锁。Go的实现是用bubble sort把channel的地址(即
Hchan*
)排序,然后依次上锁。 - 最后就是如何实现相对公平。
相对公平的另一个说法就是每个channel被选中的概率是相等的。实现如下:
for(i=0; i<sel->ncase; i++)sel->pollorder[i] = i;for(i=1; i<sel->ncase; i++) {o = sel->pollorder[i];j = runtime·fastrand1()%(i+1);sel->pollorder[i] = sel->pollorder[j];sel->pollorder[j] = o;}
每个迭代做的事情就是在前i
个元素里随机选择一个放在第i
个位置上。这个算法比programming pearls里面的难理解,因为每个元素可能被移动多次。我们分两种情况来讨论,对于任意一个位置i
,最终落在这个位置的元素可能来自i
之前(包括i
)或者i
之后。
如果是来自与i
之前(包括i
),那么它在之后就不能被交换出去。所以它留在位置i
的概率为(1/i) * i/(i+1) * (i+1)/(i+2) * ... * (n-1)/n = 1/n
。
如果来自i
之后(如位置k
),那么在换到i
之后,不能有其后的元素再和i
交换,所以概率为(1/k) * k/(k+1) * ... * (n-1)/n = 1/n
。
由以上两种情况可知,任何一个元素出现在位置i
的概率都是1/n
。
因此,按照pollorder
的顺序依次检查case是否能够执行,对于每个case来说,是公平的。
- go channel实现
- Go channel
- Go-Channel
- golang 使用 go 与 channel 实现 生产者/消费者 模型
- 使用go channel实现一个简单的信号量
- Go: channel meet range
- go语言channel关注点
- Go语言学习:Channel
- Go Channel 详解
- Go Channel 详解
- Go Channel 详解
- go 关闭channel分析
- GO channel类型
- go channel waitgroup
- 深入理解Go Channel
- Go语言Channel详解
- Go语言 channel详解
- go by example之channel-synchronization.go
- C#操作XML
- AsyncTask 详解
- HADOOP自学笔记(六)安装Hadoop环境--Trouble Shooting
- 解决找不到.so文件的办法
- 分析df和du的区别
- go channel实现
- leetCode解题报告5道题(十一)
- 用OpenSWAN做Linux下的IPSec VPN的详细配置指南
- 蓝桥杯软件大赛练习系统——基础练习 十进制转十六进制
- 湖南11省赛【A】 一二三
- Oracle基础知识笔记(9) 表的创建及管理
- 山东科技大学现最牛考研 9个宿舍40人考研全成功
- MapReduce:并行计算框架
- js里面正则表达式,匹配字符,不断更新
Post a Comment