Golang Channel用法简编九 29 bigwhite技术志 Actor, BestPractice, Blog, Blogger, Channel, Concurrent, container, CSP, docker, github, Go, Golang, Google, GopherCon, LXC, Opensource, Programmer, TIOBE, 内存模型, 博客, 容器, 工作, 布道师, 并发, 开源, 思考, 感悟, 程序员, 编程 暂无评论 在进入正式内容前,我这里先顺便转发一则消息,那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面,国内go程序员与爱好者们可以到"Golang中 国",即golangtc.com去下载go 1.3.2版本。 Go这门语言也许你还不甚了解,甚至是完全不知道,这也有情可原,毕竟Go在TIOBE编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用 Docker你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下,如 果没有涉及到“docker”字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作:《 Docker – 从入门到实践》。 据我了解,目前国内试水Go语言开发后台系统的大公司与初创公司日益增多,比如七牛、京东、小米,盛大,金山,东软,搜狗等,在这里我们可以看到一些公司的Go语言应用列表,并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中,不过目前来看多以Go入 门与基础为主题,Go idioms、tips或Best Practice的Share并不多见,想必国内的先行者、布道师们还在韬光养晦,积攒经验,等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台,比如Golang技术大会之类的的平台。 在国外,虽然Go也刚刚起步,但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多,除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外,由 Gopher Academy 发起的GopherCon 会议也于今年第一次举行,并放出诸多高质量资料,在这里可以下载。欧洲的Go语言大会.dotgo也即将开幕,估计后续这两个大会将撑起Golang技术分享 的旗帜。 言归正传,这里要写的东西并非原创,自己的Go仅仅算是入门级别,工程经验、Best Practice等还谈不上有多少,因此这里主要是针对GopherCon2014上的“舶来品”的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验,这里针对其分享的内容,记录一些学习体会和理解,并结合一些外延知识,也可以算是一种学习笔记吧,仅供参考。 一、Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP,即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样,多数Golang程序员或爱好者仅仅停留在“知道”这一层次,理解CSP理论的并不多,毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本。 维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别: Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言,又有一些根本上的不同之处: 二、Go Channel基本操作语法 Go Channel的基本操作语法如下: c := make(chan bool) //创建一个无缓冲的bool型Channel? 不带缓冲的Channel兼具通信和同步两种特性,颇受青睐。 三、Channel用作信号(Signal)的场景 1、等待一个事件(Event) 等待一个事件,有时候通过close一个Channel就足够了。例如: //testwaitevent1.go import "fmt" func main() { 这里main goroutine通过"<-c"来等待sub goroutine中的“完成事件”,sub goroutine通过close channel促发这一事件。当然也可以通过向Channel写入一个bool值的方式来作为事件通知。main goroutine在channel c上没有任何数据可读的情况下会阻塞等待。 关于输出结果: 根据《Go memory model》中关于close channel与recv from channel的order的定义:The closing of a channel happens before a receive that returns a zero value because the channel is closed. 我们可以很容易判断出上面程序的输出结果: Begin doing something! 如果将close(c)换成c<-true,则根据《Go memory model》中的定义:A receive from an unbuffered channel happens before the send on that channel completes. 2、协同多个Goroutines 同上,close channel还可以用于协同多个Goroutines,比如下面这个例子,我们创建了100个Worker Goroutine,这些Goroutine在被创建出来后都阻塞在"<-start"上,直到我们在main goroutine中给出开工的信号:"close(start)",这些goroutines才开始真正的并发运行起来。 //testwaitevent2.go import "fmt" func worker(start chan bool, index int) { func main() { 3、Select 【select的基本操作】 select { case y, ok := <- someOtherchan: case outputChan <- z: default: 【惯用法:for/select】 我们在使用select时很少只是对其进行一次evaluation,我们常常将其与for {}结合在一起使用,并选择适当时机从for{}中退出。 for { case y, ok := <- someOtherchan: case outputChan <- z: default: 【终结workers】 下面是一个常见的终结sub worker goroutines的方法,每个worker goroutine通过select监视一个die channel来及时获取main goroutine的退出通知。 //testterminateworker1.go import ( func worker(die chan bool, index int) { func main() { for i := 1; i <= 100; i++ { time.Sleep(time.Second * 5) 【终结验证】 有时候终结一个worker后,main goroutine想确认worker routine是否真正退出了,可采用下面这种方法: //testterminateworker2.go import ( func worker(die chan bool) { func main() { go worker(die) die <- true 【关闭的Channel永远不会阻塞】 下面演示在一个已经关闭了的channel上读写的结果: //testoperateonclosedchannel.go import "fmt" func main() { x, ok := <-cb ci := make(chan int) cb <- true $go run testoperateonclosedchannel.go 可以看到在一个已经close的unbuffered channel上执行读操作,回返回channel对应类型的零值,比如bool型channel返回false,int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。 【关闭带缓存的channel】 将unbuffered channel换成buffered channel会怎样?我们看下面例子: //testclosedbufferedchannel.go import "fmt" func main() { c <- 1 $go run testclosedbufferedchannel.go 可以看出带缓冲的channel略有不同。尽管已经close了,但我们依旧可以从中读出关闭前写入的3个值。第四次读取时,则会返回该channel类型的零值。向这类channel写入操作也会触发panic。 【range】 Golang中的range常常和channel并肩作战,它被用来从channel中读取所有值。下面是一个简单的实例: //testrange.go import "fmt" func generator(strings chan string) { func main() { 四、隐藏状态 下面通过一个例子来演示一下channel如何用来隐藏状态: 1、例子:唯一的ID服务 //testuniqueid.go import "fmt" func newUniqueIDService() <-chan string { $ go run testuniqueid.go newUniqueIDService通过一个channel与main goroutine关联,main goroutine无需知道uniqueid实现的细节以及当前状态,只需通过channel获得最新id即可。 五、默认情况 我想这里John Graham-Cumming主要是想告诉我们select的default分支的实践用法。 1、select for non-blocking receive idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列 select { 2、select for non-blocking send idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列 select { } 六、Nil Channels 1、nil channels阻塞 对一个没有初始化的channel进行读写操作都将发生阻塞,例子如下: package main func main() { $go run testnilchannel.go package main func main() { $go run testnilchannel.go 2、nil channel在select中很有用 看下面这个例子: //testnilchannel_bad.go import "fmt" func main() { go func() { for { 我们原本期望程序交替输出5和7两个数字,但实际的输出结果却是: 5 再仔细分析代码,原来select每次按case顺序evaluate: 我们利用nil channel来改进这个程序,以实现我们的意图,代码如下: //testnilchannel.go import "fmt" func main() { go func() { for { $go run testnilchannel.go 可以看出:通过将已经关闭的channel置为nil,下次select将会阻塞在该channel上,使得select继续下面的分支evaluation。 七、Timers 1、超时机制Timeout 带超时机制的select是常规的tip,下面是示例代码,实现30s的超时select: func worker(start chan bool) { 2、心跳HeartBeart 与timeout实现类似,下面是一个简单的心跳select实现: func worker(start chan bool) { 2014, bigwhite. 版权所有. Related posts: |
|
来自: icecity0079 > 《可能用到系统》