GoLang 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与recvfromchannel的order的定义:The closing of a channel happens before a receive that returns azerovalue because the channel is closed. 我们可以很容易判断出上面程序的输出结果: Begin doing something! 如果将close(c)换成c<-true,则根据《Go memory model》中的定义:A receivefroman 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 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) { go worker(die) die <- true 【关闭的Channel永远不会阻塞】 下面演示在一个已经关闭了的channel上读写的结果: //testoperateonclosedchannel.go func main() { x,ok := <-cb ci := make(chan int) cb <- true $go runtestoperateonclosedchannel.go 可以看到在一个已经close的unbuffered channel上执行读操作,回返回channel对应类型的零值,比如bool型channel返回false,int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。 【关闭带缓存的channel】 将unbuffered channel换成buffered channel会怎样?我们看下面例子: //testclosedbufferedchannel.go func main() { c <- 1 $go run testclosedbufferedchannel.go 可以看出带缓冲的channel略有不同。尽管已经close了,但我们依旧可以从中读出关闭前写入的3个值。第四次读取时,则会返回该channel类型的零值。向这类channel写入操作也会触发panic。 【range】 Golang中的range常常和channel并肩作战,它被用来从channel中读取所有值。下面是一个简单的实例: //testrange.go func generator(strings chan string) { func main() { 四、隐藏状态 下面通过一个例子来演示一下channel如何用来隐藏状态: 1、例子:唯一的ID服务 //testuniqueid.go 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 //用一个带缓冲的channel构造一个简单的队列 select { } 六、Nil Channels 1、nil channels阻塞 对一个没有初始化的channel进行读写操作都将发生阻塞,例子如下: package main func main() { $go run testnilchannel.go func main() { 2、nil channel在select中很有用 看下面这个例子: //testnilchannel_bad.go import "fmt" func main() { go func() { for { 我们原本期望程序交替输出5和7两个数字,但实际的输出结果却是: 5 再仔细分析代码,原来select每次按case顺序evaluate: 我们利用nil channel来改进这个程序,以实现我们的意图,代码如下: //testnilchannel.go 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) { heartbeat := time.Tick(30 * time.Second) for { select { // … do some stuff case <- heartbeat: //… do heartbeat stuff } } } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |