go channel实现
go channel实现
Go语言经过多年的发展,于最近推出了第一个稳定版本。相对于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里面。它的数据结构如下:
struct Hchan
{
uint32 qcount; // total data in the q
uint32 dataqsiz; // size of the circular q
uint16 elemsize;
bool closed;
uint8 elemalign;
Alg* elemalg; // interface for element type
uint32 sendx; // send index
uint32 recvx; // receive index
WaitQ recvq; // list of recv waiters
WaitQ sendq; // list of send waiters
Lock;
};
发送流程
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 main
func main() {
c := make(chan int)
d := make(chan int)
go func() {
<-d
close(c)
} ()
d <- 4
c <- 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来说,是公平的。
转自:http://alpha-blog.wanglianghome.org/2012/04/13/go-channel-implementation/
go channel实现的更多相关文章
- Golang, 以17个简短代码片段,切底弄懂 channel 基础
(原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...
- TODO:Go语言goroutine和channel使用
TODO:Go语言goroutine和channel使用 goroutine是Go语言中的轻量级线程实现,由Go语言运行时(runtime)管理.使用的时候在函数前面加"go"这个 ...
- GO语言之channel
前言: 初识go语言不到半年,我是一次偶然的机会认识了golang这门语言,看到他简洁的语法风格和强大的语言特性,瞬间有了学习他的兴趣.我是很看好go这样的语言的,一方面因为他有谷歌主推,另一方面他确 ...
- Critical: Update Your Windows Secure Channel (cve-2014-6321,MS14-066)
前言:风雨欲来山满楼,下半年开始各种凶猛的漏洞层出不穷,天下已经不太平,互联网已经进入一个新的台阶 0x01 cve-2014-6321 11月的补丁月,微软请windows的用户吃了顿大餐,发布了1 ...
- JAVA NIO Channel
Basic: 多数通道都是链接到开发的文件描述符的.Channel类提供维持平台独立性的抽象过程. 通道是一种途径,访问和操作操作系统,缓冲区是数据操作点: Channel类继承结构图: 通过 ...
- channel Golang
Golang, 以17个简短代码片段,切底弄懂 channel 基础 (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的 ...
- go channel
channel 是go语言中不同goroutine之间通信一种方法 //定义一个channel c := make(chan bool) //向channel中写入 c <- true //读取 ...
- [bigdata] flume file channel CPU消耗比 memory channel高的原因
https://www.quora.com/Why-does-flume-take-more-resource-CPU-when-file-channel-is-used-compared-to-wh ...
- 图解Netty之Pipeline、channel、Context之间的数据流向。
声明:本文为原创博文,禁止转载. 以下所绘制图形均基于Netty4.0.28版本. 一.connect(outbound类型事件) 当用户调用channel的connect时,会发起一个 ...
- go:channel(未完)
注:1)以下的所有讨论建立在包含整形元素的通道类型之上,即 chan int 2)对于“<-”我的理解是,它可能是一个操作符(接收操作符),也 可能是类型的一部分(如“chan<- in ...
随机推荐
- PostgreSQL Replication之第六章 监控您的设置(4)
6.4 处理监控工具 还有几个监控工具可以使您的日常生活更轻松. 其中最流行的监控工具是Nagios.它被广泛地使用,也支持各种软件组件. 要使用 Nagios 来监控您的 PostgreSQL 集群 ...
- hdu1864/2844/2159 背包基础题
hdu1864 01背包 题目链接 题目大意:一堆数,找到一个最大的和满足这个和不超过Q要学会分析复杂度! #include <cstdio> #include <cstring&g ...
- 在线运行python代码-python代码运行助手
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432523496782e ...
- Python组织文件 实践:拷贝某种类型的所有文件
#! python3 #chapter09-test01- 遍历目录树,查找特定扩展名的文件不论这些文件的位置在哪里,都将他们 #拷贝到一个新的文件夹中 import os,shutil,pprint ...
- Unity C# 设计模式(一)单例模式
动机(Motivation): 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性.以及良好的效率 意图: 保证一个类仅有一个实例,并提供一 ...
- Java基础学习总结(22)——异常处理
一.异常的概念 异常指的是运行期出现的错误,也就是当程序开始执行以后执行期出现的错误.出现错误时观察错误的名字和行号最为重要. package cn.javastudy.summary; public ...
- HttpService解析
HttpServlet容器响应Web客户请求流程如下: 1)Web客户向Servlet容器发出Http请求: 2)Servlet容器解析Web客户的Http请求: 3)Servlet容器创建一个Htt ...
- static_cast 与 dynamic_cast
- Canvas与Paint的0基础使用
看了非常多android自己定义方面的资料,了解了非常多原理,遇到人家自己定义的东西也可以看得懂,可是.当自己去自己定义的时候.发现脑袋一片空白,所以就先从认识Canvas和Paint開始吧! Can ...
- Servlet深入学习,规范,理解和实现(中)——深入理解Tomcat(一)
心得:在写这篇博客之前.我大致阅读一些关于Tomcat的书籍和博客等资料.有些资料由于时间的关系,解说的Tomcat版本号太老.有些资料能够非常好的说明Tomcat整理结构和设计思想可是非常多重要的问 ...