golang从channel读数据的各种情况
文章目录
- 用var定义channel且不make
- 用var定义channel且make
- 直给写操作加for
- 直给读操作加for
- 读写都加for
- 读channel的第二个返回值
- 关闭channel继续读
- 写完然后关闭channel再开始读
- 加个select
- channel未及时关闭
- 总结
用var定义channel且不make
wg := sync.WaitGroup{}
var ch chan stringread := func() {fmt.Println("reading")s := <-chfmt.Println("read:", s)wg.Done()
}write := func() {fmt.Println("writing")s := "t"ch <- sfmt.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 := <-chfmt.Println("read:", s)wg.Done()
}write := func() {fmt.Println("writing")s := "t"ch <- sfmt.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 := <-chfmt.Println("read:", s)wg.Done()
}write := func() {for {fmt.Println("writing")s := "t"ch <- sfmt.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 := <-chfmt.Println("read:", s)}wg.Done()
}write := func() {fmt.Println("writing")s := "t"ch <- sfmt.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 := <-chfmt.Println("read:", s)}wg.Done()
}write := func() {for {fmt.Println("writing")s := "t"ch <- sfmt.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 := <-chfmt.Println("read:", s, ok)}wg.Done()
}write := func() {for {fmt.Println("writing")s := "t"ch <- sfmt.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 := <-chfmt.Println("read:", len(s), s, ok)}wg.Done()
}write := func() {for i := 0; i < 5; i++ {fmt.Println("writing")s := "t"ch <- sfmt.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 := <-chfmt.Println("read:", len(s), s, ok)}wg.Done()
}write := func() {for i := 0; i < 5; i++ {fmt.Println("writing")s := "t"ch <- sfmt.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 <- sfmt.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 <- sfmt.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读数据的各种情况相关推荐
- GoLang之channel 在什么情况下会引起资源泄漏(10)
文章目录 GoLang之channel 在什么情况下会引起资源泄漏(10) GoLang之channel 在什么情况下会引起资源泄漏(10) Channel 可能会引发 goroutine 泄漏. 泄 ...
- golang 中 channel 的详细使用、使用注意事项及死锁分析
什么是 channel 管道 它是一个数据管道,可以往里面写数据,从里面读数据. channel 是 goroutine 之间数据通信桥梁,而且是线程安全的. channel 遵循先进先出原则. 写入 ...
- golang中channel使用
1 golang中channel使用 文章目录 1 golang中channel使用 1.1 channel介绍 1.2 channel使用 1.2.1 channel声明和初始化 1.2.2 cha ...
- golang中Channel通道(二)
golang中Channel通道(二) 一.带缓冲和不带缓冲的通道的区别 1.非缓冲通道 一次发送操作对应一次接收操作,对于一个goroutine来讲,它的一次发送,在另一个goroutine接收之前 ...
- golang的channel实现原理
golang的channel实现原理 chan结构 src/runtime/chan.go type hchan struct {qcount uint // 当前队列中剩余元素个数dataqsiz ...
- GoLang之channel底层的数据结构是什么、channel的创建(2)
文章目录 GoLang之channel底层的数据结构是什么.channel的创建(2) 1.数据结构 2.创建 GoLang之channel底层的数据结构是什么.channel的创建(2) 1.数据结 ...
- Golang关于channel死锁情况的汇总以及解决方案
直接读取空channel的死锁 当一个channel中没有数据,而直接读取时,会发生死锁: func main() {q := make(chan int, 2)<-q } 错误提示: fata ...
- Golang笔记——channel(管道)
推荐首先阅读:Golang笔记--goroutine(协程) 为什么需要 channel 前面使用全局变量加锁同步来解决 goroutine 的通讯,但不完美 主线程在等待所有 goroutine 全 ...
- golang的Channel初始化的有缓存与无缓存解释
首先编程的时候遇到疑问,输出跟我所想预想不一样,后来查到了golang社区的帖子,其中一篇帖子 :健哥大人 做出了一些解释. 我摘抄重点过来: 无缓冲的与有缓冲channel有着重大差别,那就是一个 ...
最新文章
- Java8 Lamdba表达式 002
- php this 代表什么,php中$this-是什么意义
- 2020-11-18(失败的一天)
- 微信小程序开发系列五:微信小程序中如何响应用户输入事件
- GC:垃圾回收机制及算法
- Windows域控去掉密码强度策略 可以设置简单密码【全域策略生效】
- C语言 — 运算符的优先级与结合性
- c 程序设计语言简单列子,C语言程序设计实例大全(220个例子)
- Windows重新生成UEFI引导,解决Windows蓝屏\BCD 0xc0000098
- 2019最新《网易云课堂C++开发工程师案例-网吧收银系统(MFC+ADO)》
- uni-app项目利用HBuilder X工具使用命令一键自动编译导出APP资源
- 观点 | 量子卫星很近,但“无法破解”的网络可能还很远
- Collecting package metadata (current_repodata.json): failed(解决方案)
- NX二次开发-UFUN创建圆柱UF_MODL_create_cyl1
- 测开学习篇-html
- 放射组学常用到的一些工具(软件)
- 嵌入式编程中volatile的重要性
- PHP面向对象技术(全面讲解)(高洛峰)
- 指向结构体的指针和指向结构体指针的指针
- 如何评估工时和开发计划