golang从channel读数据的各种情况
Posted -_-void
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang从channel读数据的各种情况相关的知识,希望对你有一定的参考价值。
文章目录
用var定义channel且不make
wg := sync.WaitGroup
var ch chan string
read := func()
fmt.Println("reading")
s := <-ch
fmt.Println("read:", s)
wg.Done()
write := func()
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
输出:
waiting
writing
reading
fatal error: all goroutines are asleep - deadlock!
这种情况并不是报错空指针,而是死锁。加上make看看
用var定义channel且make
wg := sync.WaitGroup
var ch = make(chan string)
read := func()
fmt.Println("reading")
s := <-ch
fmt.Println("read:", s)
wg.Done()
write := func()
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
wg.Add(2)
go read()
go write()
输出
waiting
writing
reading
read: t
write: t
这种情况没什么毛病,之所以先输出的read,是因为IO机制。下面给写加上for
直给写操作加for
wg := sync.WaitGroup
var ch = make(chan string)
read := func()
fmt.Println("reading")
s := <-ch
fmt.Println("read:", s)
wg.Done()
write := func()
for
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
输出
waiting
reading
writing
write: t
writing
read: t
fatal error: all goroutines are asleep - deadlock!
报错说所有的协程都睡着,意思就是runtime发现没有能拿来调度的协程了,报错退出。如果是在大项目中,这里则会阻塞,runtime会调度其他可运行的协程。下面把for移到读操作上。
直给读操作加for
wg := sync.WaitGroup
var ch = make(chan string)
read := func()
for
fmt.Println("reading")
s := <-ch
fmt.Println("read:", s)
wg.Done()
write := func()
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
输出
waiting
reading
writing
write: t
read: t
reading
fatal error: all goroutines are asleep - deadlock!
跟上面现象基本一样,不再赘述,然后给俩操作都加上for
读写都加for
wg := sync.WaitGroup
var ch = make(chan string)
read := func()
for
fmt.Println("reading")
s := <-ch
fmt.Println("read:", s)
wg.Done()
write := func()
for
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
输出
waiting
writing
reading
read: t
write: t
writing
reading
read: t
reading
write: t
writing
write: t
writing
...
结果当然就是死循环了,这个很好理解。接下来才是本文的重点:读数据的第二个参数。我们先保持其他的都不动,在读的时候接收第二个返回值。
读channel的第二个返回值
wg := sync.WaitGroup
var ch = make(chan string)
read := func()
for
fmt.Println("reading")
s, ok := <-ch
fmt.Println("read:", s, ok)
wg.Done()
write := func()
for
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
输出
waiting
writing
reading
read: t true
reading
write: t
writing
write: t
writing
read: t true
reading
read: t true
reading
write: t
...
可以看出来,这第二个返回值是个bool类型,目前全都是true。那么什么时候会是false呢,把channel关上试试。为了更直观,把字符串的长度一起输出
关闭channel继续读
wg := sync.WaitGroup
var ch = make(chan string)
read := func()
for
fmt.Println("reading")
s, ok := <-ch
fmt.Println("read:", len(s), s, ok)
wg.Done()
write := func()
for i := 0; i < 5; i++
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
close(ch)
wg.Add(2)
go read()
go write()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
输出
waiting
writing
reading
read: 1 t true
reading
write: t
writing
write: t
writing
read: 1 t true
reading
read: 1 t true
reading
write: t
writing
write: t
writing
read: 1 t true
reading
read: 1 t true
reading
write: t
read: 0 false
reading
read: 0 false
reading
read: 0 false
...
接下来就是很规律的死循环了。这样是不是可以猜测,从已经close的channle读数据,会读到该数据类型的零值,且第二个返回值为false?再试试给channel加个buffer,先写完关上再开始读
写完然后关闭channel再开始读
wg := sync.WaitGroup
var ch = make(chan string, 5)
read := func()
for
fmt.Println("reading")
s, ok := <-ch
fmt.Println("read:", len(s), s, ok)
wg.Done()
write := func()
for i := 0; i < 5; i++
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
close(ch)
fmt.Println("closed")
wg.Add(2)
write()
go read()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
输出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
closed
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 0 false
reading
read: 0 false
reading
read: 0 false
...
我们把写操作前的go
关键字去了,并且在关闭channel之后加了log。可以很清晰的看到,先往channel里写了5次,然后close了,之后才有wait及read的log。并且前5个ok是true,后面循环输出false。现在我们可以得出结论当channel关闭且数据都读完了,再读数据会读到该数据类型的零值,且第二个返回值为false。下面再套上select
加个select
wg := sync.WaitGroup
var ch = make(chan string, 5)
read := func()
for
fmt.Println("reading")
select
case s, ok := <-ch:
fmt.Println("read:", len(s), s, ok)
wg.Done()
write := func()
for i := 0; i < 5; i++
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
close(ch)
fmt.Println("closed")
wg.Add(2)
write()
go read()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
输出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
closed
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 0 false
reading
read: 0 false
reading
read: 0 false
...
很明显跟上面现象一致,如果忘了关闭channel呢?
channel未及时关闭
wg := sync.WaitGroup
var ch = make(chan string, 5)
read := func()
for
fmt.Println("reading")
select
case s, ok := <-ch:
fmt.Println("read:", len(s), s, ok)
wg.Done()
write := func()
for i := 0; i < 5; i++
fmt.Println("writing")
s := "t"
ch <- s
fmt.Println("write:", s)
wg.Done()
//close(ch)
//fmt.Println("closed")
wg.Add(2)
write()
go read()
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
输出
writing
write: t
writing
write: t
writing
write: t
writing
write: t
writing
write: t
waiting
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
read: 1 t true
reading
fatal error: all goroutines are asleep - deadlock!
又睡着了,然后报错。跟上面情况一样,如果是在大项目中,runtime会调度其他可运行的协程。最后来总结一下怎么操作才算优(sao)雅(qi)。
总结
- 对写的一方来说,一定记着及时关闭channel,避免出现协程泄露。虽然它占得资源少,省点电不香么。
- 对读的一方来说,除非十分确定数据的个数,最好是用for来读数据,省的在“管儿”里有“野数据”造成内存泄露。同时根据第二个返回值的真假来控制for循环,避免出现“无效工作量”
以上是关于golang从channel读数据的各种情况的主要内容,如果未能解决你的问题,请参考以下文章