内容简介:本来觉得自己的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用法笔记》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Nature of Code
Daniel Shiffman / The Nature of Code / 2012-12-13 / GBP 19.95
How can we capture the unpredictable evolutionary and emergent properties of nature in software? How can understanding the mathematical principles behind our physical world help us to create digital w......一起来看看 《The Nature of Code》 这本书的介绍吧!