内容简介:本来觉得自己的golang的channel已经有所了解了,但实际在被问一些问题的时候,还是没法回答的非常明确,所以今天又梳理整理了一遍,并且把容易出错的问题记录一下。废话不多说,先上一段代码:想一下输出结果应该是什么呢?我承认这个一开始确实自己没有看仔细,
本来觉得自己的golang的channel已经有所了解了,但实际在被问一些问题的时候,还是没法回答的非常明确,所以今天又梳理整理了一遍,并且把容易出错的问题记录一下。
废话不多说,先上一段代码:
package main import ( "fmt" ) func Count(ch chan int, n int) { fmt.Println("Counting ", n) ch <- n } func main() { chs := make([]chan int, 10) for i := 0; i < 10; i++ { chs[i] = make(chan int) go Count(chs[i], i) } for _, ch := range chs { v := <-ch fmt.Println("v ", v) } }
想一下输出结果应该是什么呢?我承认这个一开始确实自己没有看仔细, chs
是一个信道数组,他的每一个参数都是重新new的一个信道,所以输出结果应该是这样:
Counting 9 Counting 0 v 0 Counting 1 v 1 Counting 6 ... 全部会输出,并且顺序不一定
我一开始想,这个信道不是没有缓存吗?应该读一个取一个才对,但是没看到,其实数组里面的每一个参数都是重新new的信道。那什么情况才会是我想象中的输出结果呢?代码可以改成这样,只改main函数就可以:
func main() { ch := make(chan int) for i := 0; i < 10; i++ { go Count(ch, i) } for i := 0; i < 10; i++ { v := <-ch fmt.Println("v ", v) } }
这个时候,就是读一个取一个,因为只有一个信道,输出结果:
Counting 9 v 9 Counting 0 v 0 Counting 1 v 1 Counting 2 v 2 ...
这个时候,我想用一个带缓存的信道进行存储,然后用range进行读取,代码就改成了这样:
func Count(ch chan int, n int) { fmt.Println("Counting ", n) ch <- n } func main() { ch := make(chan int, 10) //改成有缓存的信道 for i := 0; i < 10; i++ { go Count(ch, i) } for v := range ch { fmt.Println("v ", v) } }
结果输出的时候报错了:
... Counting 8 v 8 fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /home/tfd/workspace/go/src/test/main.go:24 +0x126
死锁了,原来是因为 range
不会检测信道是否干涸(drained,也就是信道是否已经读取完),在读取完所有数据后,再次读取,会导致main函数挂起。那怎么办呢,第一种方法,如果我知道一共缓存了多少数据,那我就读多少数据就可以了,代码如下:
func main() { ch := make(chan int, 10) for i := 0; i < 10; i++ { go Count(ch, i) } for i := 0; i < 10; i++ { //这边假设我知道一共存了10个数据,那就读10次就好了,如果读11次,那就会报deadlock错误 v := <-ch fmt.Println("v ", v) } }
读取结果如下:
Counting 9 v 9 Counting 0 v 0 Counting 1 v 1 Counting 2 v 2 Counting 3 v 3 Counting 7 v 7 Counting 6 v 6 Counting 8 v 8 Counting 4 Counting 5 v 4 v 5
那实际现实中,我很大可能不知道一共写入了多少次,那怎么办呢?我们可以用 sync.WaitGroup
, 简单介绍下 WaitGroup
对象内部有一个计数器,最初从0开始,它一共包含三个方法: Add()
, Done()
, Wait()
用来控制计数器的数量。 Add(n)
把计数器设置为 n
, Done()
每次把计数器 -1
, Wait()
会阻塞代码的运行,直到计数器的值减为0. 那我们用 WaitGroup
改一下代码就可以了。
func Count(ch chan int, n int, wg *sync.WaitGroup) { defer wg.Done() fmt.Println("Counting ", n) ch <- n } func main() { //wg := sync.WaitGroup{} //这里可以用new(sync.WaitGroup),这样直接就可以得到指针类型的WaitGroup wg:= new(sync.WaitGroup) ch := make(chan int, 10) for i := 0; i < 10; i++ { wg.Add(1) go Count(ch, i, wg) } go func() { wg.Wait() close(ch) fmt.Println("Finish") }() for v := range ch { fmt.Println("v:", v) } }
这时,代码运行就没问题了,输出结果
Counting 5 Counting 4 v: 5 v: 4 Counting 2 v: 2 Counting 6 Counting 1 v: 6 Counting 0 Counting 8 Counting 3 v: 1 v: 0 v: 8 v: 3 Counting 7 v: 7 Counting 9 Finish v: 9
但是在这里我也是遇到坑的,一开始 WaitGroup
没有传指针,代码是这样的:
func Count(ch chan int, n int, wg sync.WaitGroup) { //wg没有传指针 defer wg.Done() fmt.Println("Counting ", n) ch <- n } func main() { wg := sync.WaitGroup{} ch := make(chan int, 10) for i := 0; i < 10; i++ { wg.Add(1) go Count(ch, i, wg) //wg没有传指针 } go func() { wg.Wait() close(ch) fmt.Println("Finish") }() for v := range ch { fmt.Println("v:", v) } }
这个时候,代码仍然报deadlock,结果我就不贴了。这是因为这个时候wg是将拷贝传递到了goroutine中,所以,wg只有Add操作,而Done操作是在wg的副本中执行的,因此Wait就死锁了。正确的应该是要传指针,上一段代码已经把正确的写进去了。
这里还可以注意一点就是,信道 close
了之后可以读但是不可以写,并且, close
之后的信道是不会阻塞的,所以才可以通过 WaitGroup
的方法在数据写完之后把信道 close
就不会出现阻塞的情况了。
以上所述就是小编给大家介绍的《2018-11-06 golang 中Channel用法笔记》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
应用密码学:协议、算法与C源程序(原书第2版)
(美)Bruce Schneier / 吴世忠、祝世雄、张文政 等 / 机械工业出版社 / 2014-1 / 79.00
......我所读过的关于密码学最好的书......该书是美国国家安全局最不愿意见到出版的书...... —— 《Wired》 ......不朽的......令人着迷的......计算机程序员必读的密码学上决定性的著作...... —— 《Dr.Dobb's Journal》 ......该领域勿庸置疑的一本权威之作。 —— 《PC Magazine》 ..........一起来看看 《应用密码学:协议、算法与C源程序(原书第2版)》 这本书的介绍吧!