信道(channel)是Goroutine之间通信的手段,所以,掌握Channel的用法之于Golang的学习变得尤为重要。
信道到底是怎么一种东西?
也许信道与 UNIX的双向PIPE 比较类似,也是采用了FIFO的方式来实现调度。由于Goroutine 运行在相同的地址空间 , 所以访问共享内存时必须要做好同步 。Channel就是为了解决这个问题而出现的。通过他来接收或者发送值。
Donot BB Show me the code
1 2 3 4 5 6 7 8
| func main() { init:=make(chan int,20) init<-1 init<-2 fmt.Println(<-init) fmt.Println(<-init) }
|
这就是Channel的用法了,首先缓冲数据,再取出数据。
By the way,信道的发送和接收方默认是阻塞的,也就是,知道发送方有数据发送过来之前,接收的通道一直都是阻塞的。
当然,这是缓冲Channel的写法。可以缓冲多组数据。
而无缓冲的channel(即 make(chan TYPE) )只能用于单组数据的传输,这也是goroutine最常用的方式。
Goroutine基本运用
用channel来辅助并发(Coroutine) 应该是channel最基础的应用.由于在主线程(main)会在go func()之前跑完,所以导致func()没法跑完 用channel实现的思路是 用channel去阻塞主线程,直到个go func()全部跑完 再把阻塞取消掉。在多个Go Routine中,需要维护一个全局的channel数组,由这个全局channel控制每个Goroutine。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package main import "fmt" func main() { fmt.Println("main routine..") charr := make([]chan int, 11) charr[0] = make(chan int) //One GoR go func(ch chan int) { fmt.Println("other routine") ch <- 1 }(charr[0]) //Other GoRs for chn := 1; chn < 11; chn++ { charr[chn] = make(chan int) go func(ch chan int) { for i := 0; i < 3; i++ { fmt.Println(i) ch <- 1 } }(charr[chn]) } //解除阻塞.. for _, ch := range charr { <-ch } }
|
稍微对着代码解释下,charr是我设置的全局channel数组 由他负责所有的子Channel
关于Range和Close在channel中的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main import ( "fmt" ) func channel_init(c chan int){ for i:=0;i<20;i++{ c <- i } close(c) } func main() { init:=make(chan int,20) channel_init(init) for data:=range init{ fmt.Println(data) } }
|
这里的range实际是遍历的一种简写方法。作用也是遍历channel里的所有数据。range给我们带来了很大的便利
而close(),便是关闭当前信道。注意!close一定要在生产的地方去调用,在消费的地方调用会引起panic.channel作为一种缓存类型,并不需要经常去关闭,只要在确实没有数据发的时候关闭信道就可以了。
升阶--让我们在一起吧
思想:在遇到非常复杂的计算时,我们可以分多路对此计算进行分割计算,之后再把三路的计算结果放入总信道,然后从总信道里取值,以此来缩短程序的运行时间。
The Code is here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package main import ( "fmt" "time" "math/rand" ) func simu_cacul(x int) int{ time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) return 50-x } func branch(data int) chan int{ //构造branch branch:=make(chan int) //匿名函数表达式执行 go func(){ //部分计算过程 放入这个分支 branch <- simu_cacul(data) }() return branch } //合并全部分支 func merge(aChan ...chan int)chan int{ //aChan是一个Slice类型 result:=make(chan int) for _,v:=range aChan{ //merge 取出传入信道的数据传到result信道 go func(c chan int){result <- <-c}(v) } return result; } func main() { res:=merge(branch(1),branch(2),branch(3)) for i:=0;i<3;i++{ fmt.Println(<-res) } }
|
(嘘!这里我们假装50-x是一个非常耗时的计算。。。用了Sleep来强行伪装。。。。)
监视器
Golang中黑科技炒鸡多的,这就是我喜欢它的原因吧~~比如这个SELECT是用来监测信道上的数据流动
select默认是阻塞的,只有当监听的信道接收或发送时才可以运行,然后当有多个信道满足条件时,select会选择一个执行。(感觉用法跟Switch‘有点像 然而只是语法有点像 case default啥的
然后上面的代码就可以开始简化了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| //合并全部分支 func merge(aChan ...chan int)chan int{ //aChan是一个Slice类型 result:=make(chan int) go func(){ for _,v:=range aChan{ select{ //监听从v信道流出 case c:=<-v: result <- c } } }() return result; }
|
SELECT 还有一个 设置超时 功能,用来避免全部阻塞的情况
case <-time.After(5*time.Second):
//do sth here