golang channel 用法转的
一、Golang并发基础理论
Golang在并发设计方面参考了C.A.R Hoare的CSP,即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样,多数Golang程序员或爱好者仅仅停留在“知道”这一层次,理解CSP理论的并不多,毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本。
维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别:
Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言,又有一些根本上的不同之处:
– CSP模型处理过程是匿名的,而Actor模型中的Actor则具有身份标识。
– CSP模型的消息传递在收发消息进程间包含了一个交会点,即发送方只能在接收方准备好接收消息时才能发送消息。相反,actor模型中的消息传递是异步 的,即消息的发送和接收无需在同一时间进行,发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下,基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。
– CSP使用显式的Channel用于消息传递,而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下,进程可 以从一个实际上拥有身份标识的channel接收消息,而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。
二、Go Channel基本操作语法
Go Channel的基本操作语法如下:
c := make(chan bool) //创建一个无缓冲的bool型Channel
c <- x //向一个Channel发送一个值
<- c //从一个Channel中接收一个值
x = <- c //从Channel c接收一个值并将其存储到x中
x, ok = <- c //从Channel接收一个值,如果channel关闭了或没有数据,那么ok将被置为false
不带缓冲的Channel兼具通信和同步两种特性,颇受青睐。
三、Channel用作信号(Signal)的场景
1、等待一个事件(Event)
等待一个事件,有时候通过close一个Channel就足够了。例如:
//testwaitevent1.go
package main
import "fmt"
func main() {
fmt.Println("Begin doing something!")
c := make(chan bool)
go func() {
fmt.Println("Doing something…")
close(c)
}()
<-c
fmt.Println("Done!")
}
这里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!
Doing something…
Done!
如果将close(c)换成c<-true,则根据《Go memory model》中的定义:A receive from an unbuffered channel happens before the send on that channel completes.
"<-c"要先于"c<-true"完成,但也不影响日志的输出顺序,输出结果仍为上面三行。
2、协同多个Goroutines
同上,close channel还可以用于协同多个Goroutines,比如下面这个例子,我们创建了100个Worker Goroutine,这些Goroutine在被创建出来后都阻塞在"<-start"上,直到我们在main goroutine中给出开工的信号:"close(start)",这些goroutines才开始真正的并发运行起来。
//testwaitevent2.go
package main
import "fmt"
func worker(start chan bool, index int) {
<-start
fmt.Println("This is Worker:", index)
}
func main() {
start := make(chan bool)
for i := 1; i <= 100; i++ {
go worker(start, i)
}
close(start)
select {} //deadlock we expected
}
3、Select
【select的基本操作】
select是Go语言特有的操作,使用select我们可以同时在多个channel上进行发送/接收操作。下面是select的基本操作。
select {
case x := <- somechan:
// … 使用x进行一些操作
case y, ok := <- someOtherchan:
// … 使用y进行一些操作,
// 检查ok值判断someOtherchan是否已经关闭
case outputChan <- z:
// … z值被成功发送到Channel上时
default:
// … 上面case均无法通信时,执行此分支
}
【惯用法:for/select】
我们在使用select时很少只是对其进行一次evaluation,我们常常将其与for {}结合在一起使用,并选择适当时机从for{}中退出。
for {
select {
case x := <- somechan:
// … 使用x进行一些操作
case y, ok := <- someOtherchan:
// … 使用y进行一些操作,
// 检查ok值判断someOtherchan是否已经关闭
case outputChan <- z:
// … z值被成功发送到Channel上时
default:
// … 上面case均无法通信时,执行此分支
}
}
【终结workers】
下面是一个常见的终结sub worker goroutines的方法,每个worker goroutine通过select监视一个die channel来及时获取main goroutine的退出通知。
//testterminateworker1.go
package main
import (
"fmt"
"time"
)
func worker(die chan bool, index int) {
fmt.Println("Begin: This is Worker:", index)
for {
select {
//case xx:
//做事的分支
case <-die:
fmt.Println("Done: This is Worker:", index)
return
}
}
}
func main() {
die := make(chan bool)
for i := 1; i <= 100; i++ {
go worker(die, i)
}
time.Sleep(time.Second * 5)
close(die)
select {} //deadlock we expected
}
【终结验证】
有时候终结一个worker后,main goroutine想确认worker routine是否真正退出了,可采用下面这种方法:
//testterminateworker2.go
package main
import (
"fmt"
//"time"
)
func worker(die chan bool) {
fmt.Println("Begin: This is Worker")
for {
select {
//case xx:
//做事的分支
case <-die:
fmt.Println("Done: This is Worker")
die <- true
return
}
}
}
func main() {
die := make(chan bool)
go worker(die)
die <- true
<-die
fmt.Println("Worker goroutine has been terminated")
}
【关闭的Channel永远不会阻塞】
下面演示在一个已经关闭了的channel上读写的结果:
//testoperateonclosedchannel.go
package main
import "fmt"
func main() {
cb := make(chan bool)
close(cb)
x := <-cb
fmt.Printf("%#v\n", x)
x, ok := <-cb
fmt.Printf("%#v %#v\n", x, ok)
ci := make(chan int)
close(ci)
y := <-ci
fmt.Printf("%#v\n", y)
cb <- true
}
$go run testoperateonclosedchannel.go
false
false false
0
panic: runtime error: send on closed channel
可以看到在一个已经close的unbuffered channel上执行读操作,回返回channel对应类型的零值,比如bool型channel返回false,int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。
【关闭带缓存的channel】
将unbuffered channel换成buffered channel会怎样?我们看下面例子:
//testclosedbufferedchannel.go
package main
import "fmt"
func main() {
c := make(chan int, 3)
c <- 15
c <- 34
c <- 65
close(c)
fmt.Printf("%d\n", <-c)
fmt.Printf("%d\n", <-c)
fmt.Printf("%d\n", <-c)
fmt.Printf("%d\n", <-c)
c <- 1
}
$go run testclosedbufferedchannel.go
15
34
65
0
panic: runtime error: send on closed channel
可以看出带缓冲的channel略有不同。尽管已经close了,但我们依旧可以从中读出关闭前写入的3个值。第四次读取时,则会返回该channel类型的零值。向这类channel写入操作也会触发panic。
【range】
Golang中的range常常和channel并肩作战,它被用来从channel中读取所有值。下面是一个简单的实例:
//testrange.go
package main
import "fmt"
func generator(strings chan string) {
strings <- "Five hour's New York jet lag"
strings <- "and Cayce Pollard wakes in Camden Town"
strings <- "to the dire and ever-decreasing circles"
strings <- "of disrupted circadian rhythm."
close(strings)
}
func main() {
strings := make(chan string)
go generator(strings)
for s := range strings {
fmt.Printf("%s\n", s)
}
fmt.Printf("\n")
}
四、隐藏状态
下面通过一个例子来演示一下channel如何用来隐藏状态:
1、例子:唯一的ID服务
//testuniqueid.go
package main
import "fmt"
func newUniqueIDService() <-chan string {
id := make(chan string)
go func() {
var counter int64 = 0
for {
id <- fmt.Sprintf("%x", counter)
counter += 1
}
}()
return id
}
func main() {
id := newUniqueIDService()
for i := 0; i < 10; i++ {
fmt.Println(<-id)
}
}
$ go run testuniqueid.go
0
1
2
3
4
5
6
7
8
9
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 {
case b = <-idle:
//尝试从idle队列中读取
…
default: //队列空,分配一个新的buffer
makes += 1
b = make([]byte, size)
}
2、select for non-blocking send
idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列
select {
case idle <- b: //尝试向队列中插入一个buffer
//…
default: //队列满?
}
六、Nil Channels
1、nil channels阻塞
对一个没有初始化的channel进行读写操作都将发生阻塞,例子如下:
package main
func main() {
var c chan int
<-c
}
$go run testnilchannel.go
fatal error: all goroutines are asleep – deadlock!
package main
func main() {
var c chan int
c <- 1
}
$go run testnilchannel.go
fatal error: all goroutines are asleep – deadlock!
2、nil channel在select中很有用
看下面这个例子:
//testnilchannel_bad.go
package main
import "fmt"
import "time"
func main() {
var c1, c2 chan int = make(chan int), make(chan int)
go func() {
time.Sleep(time.Second * 5)
c1 <- 5
close(c1)
}()
go func() {
time.Sleep(time.Second * 7)
c2 <- 7
close(c2)
}()
for {
select {
case x := <-c1:
fmt.Println(x)
case x := <-c2:
fmt.Println(x)
}
}
fmt.Println("over")
}
我们原本期望程序交替输出5和7两个数字,但实际的输出结果却是:
5
0
0
0
… … 0死循环
再仔细分析代码,原来select每次按case顺序evaluate:
– 前5s,select一直阻塞;
– 第5s,c1返回一个5后被close了,“case x := <-c1”这个分支返回,select输出5,并重新select
– 下一轮select又从“case x := <-c1”这个分支开始evaluate,由于c1被close,按照前面的知识,close的channel不会阻塞,我们会读出这个 channel对应类型的零值,这里就是0;select再次输出0;这时即便c2有值返回,程序也不会走到c2这个分支
– 依次类推,程序无限循环的输出0
我们利用nil channel来改进这个程序,以实现我们的意图,代码如下:
//testnilchannel.go
package main
import "fmt"
import "time"
func main() {
var c1, c2 chan int = make(chan int), make(chan int)
go func() {
time.Sleep(time.Second * 5)
c1 <- 5
close(c1)
}()
go func() {
time.Sleep(time.Second * 7)
c2 <- 7
close(c2)
}()
for {
select {
case x, ok := <-c1:
if !ok {
c1 = nil
} else {
fmt.Println(x)
}
case x, ok := <-c2:
if !ok {
c2 = nil
} else {
fmt.Println(x)
}
}
if c1 == nil && c2 == nil {
break
}
}
fmt.Println("over")
}
$go run testnilchannel.go
5
7
over
可以看出:通过将已经关闭的channel置为nil,下次select将会阻塞在该channel上,使得select继续下面的分支evaluation。
七、Timers
1、超时机制Timeout
带超时机制的select是常规的tip,下面是示例代码,实现30s的超时select:
func worker(start chan bool) {
timeout := time.After(30 * time.Second)
for {
select {
// … do some stuff
case <- timeout:
return
}
}
}
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
}
}
}
golang channel 用法转的的更多相关文章
- Golang channel 用法简介
channel 是 golang 里相当有趣的一个功能,大部分时候 channel 都是和 goroutine 一起配合使用.本文主要介绍 channel 的一些有趣的用法. 通道(channel), ...
- Golang Channel用法简编
转自:http://tonybai.com/2014/09/29/a-channel-compendium-for-golang/ 在进入正式内容前,我这里先顺便转发一则消息,那就是Golang 1. ...
- golang channel的使用以及调度原理
golang channel的使用以及调度原理 为了并发的goroutines之间的通讯,golang使用了管道channel. 可以通过一个goroutines向channel发送数据,然后从另一个 ...
- Go基础系列:nil channel用法示例
Go channel系列: channel入门 为select设置超时时间 nil channel用法示例 双层channel用法示例 指定goroutine的执行顺序 当未为channel分配内存时 ...
- Go基础系列:双层channel用法示例
Go channel系列: channel入门 为select设置超时时间 nil channel用法示例 双层channel用法示例 指定goroutine的执行顺序 双层通道的解释见Go的双层通道 ...
- Go语言的管道Channel用法
本文实例讲述了Go语言的管道Channel用法.分享给大家供大家参考.具体分析如下: channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值. ch <- ...
- golang channel关闭后,是否可以读取剩余的数据
golang channel关闭后,其中剩余的数据,是可以继续读取的. 请看下面的测试例子. 创建一个带有缓冲的channel,向channel中发送数据,然后关闭channel,最后,从channe ...
- golang channel原理
channel介绍 channel一个类型管道,通过它可以在goroutine之间发送和接收消息.它是Golang在语言层面提供的goroutine间的通信方式. 众所周知,Go依赖于称为CSP(Co ...
- golang channel 源码剖析
channel 在 golang 中是一个非常重要的特性,它为我们提供了一个并发模型.对比锁,通过 chan 在多个 goroutine 之间完成数据交互,可以让代码更简洁.更容易实现.更不容易出错. ...
随机推荐
- jQuery easyUI datagrid 增加求和统计行 分类: JavaScript 2015-01-14 17:46 2178人阅读 评论(0) 收藏
在datagrid的onLoadSuccess事件增加代码处理. <style type="text/css"> .subtotal { font-weight: bo ...
- JavaScript - 基本概念
相等操作符 == 和 != 是先转换再比较 === 和 !== 是直接比较,不经过转换 中文翻译上来说叫做相等和全等.类型转换简单的概括就是高级转低级,比如说布尔字符串转数字,对象取值再转.相等比较有 ...
- hdu 4273 2012长春赛区网络赛 三维凸包中心到最近面距离 ***
新模板 /* HDU 4273 Rescue 给一个三维凸包,求重心到表面的最短距离 模板题:三维凸包+多边形重心+点面距离 */ #include<stdio.h> #include&l ...
- 跟着鸟哥学Linux系列笔记1
跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 装完linux之后,接下来一步就是进行相关命令的学习了 第五章:首次登录与在线求助man page 1. X ...
- 把VSO作为GitHub上JavaScript项目的免费CI服务器
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:微软变得更加开放后,走向开放的不仅有.NET运行时.IDE工具,还有ALM服务器核心组 ...
- 使用Jmeter进行http接口性能测试
在进行网页或应用程序后台接口开发时,一般要及时测试开发的接口能否正确接收和返回数据,对于单次测试,Postman插件是个不错的Http请求模拟工具. 但是Postman只能模拟单客户端的单次请求,而对 ...
- 模板模式与策略模式/template模式与strategy模式/行为型模式
模板模式 模版模式,又被称为模版方法模式,它可以将工作流程进行封装,并且对外提供了个性化的控制,但主流程外界不能修改,也就是说,模版方法模式中,将工作的主体架构规定好,具体类可以根据自己的需要,各自去 ...
- Linux学习笔记(19) Linux服务管理
1. 服务的分类 Linux服务可分为RPM包默认安装的服务和源码包安装的服务.前者可细分为独立的服务(直接作用于内存中)和基于xinetd服务.xinetd本身是独立的服务,其唯一的功能是管理其他服 ...
- supervisor(三)xml_rpc
supervisor提供的两种管理方式,supervisorctl和web其实都是通过xml_rpc来实现的. xml_rpc其实就是本地可以去调用远端的函数方法,然后函数方法经过一番处理后,把结果返 ...
- 【框架】网络请求+Gson解析--Retrofit 2
其实内部是封装了Okhttp和Gson解析 public class CourseFragmentAPI { public static void get(String userId, BaseCal ...