文章目录

  • 用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读数据的各种情况相关推荐

  1. GoLang之channel 在什么情况下会引起资源泄漏(10)

    文章目录 GoLang之channel 在什么情况下会引起资源泄漏(10) GoLang之channel 在什么情况下会引起资源泄漏(10) Channel 可能会引发 goroutine 泄漏. 泄 ...

  2. golang 中 channel 的详细使用、使用注意事项及死锁分析

    什么是 channel 管道 它是一个数据管道,可以往里面写数据,从里面读数据. channel 是 goroutine 之间数据通信桥梁,而且是线程安全的. channel 遵循先进先出原则. 写入 ...

  3. golang中channel使用

    1 golang中channel使用 文章目录 1 golang中channel使用 1.1 channel介绍 1.2 channel使用 1.2.1 channel声明和初始化 1.2.2 cha ...

  4. golang中Channel通道(二)

    golang中Channel通道(二) 一.带缓冲和不带缓冲的通道的区别 1.非缓冲通道 一次发送操作对应一次接收操作,对于一个goroutine来讲,它的一次发送,在另一个goroutine接收之前 ...

  5. golang的channel实现原理

    golang的channel实现原理 chan结构 src/runtime/chan.go type hchan struct {qcount uint // 当前队列中剩余元素个数dataqsiz ...

  6. GoLang之channel底层的数据结构是什么、channel的创建(2)

    文章目录 GoLang之channel底层的数据结构是什么.channel的创建(2) 1.数据结构 2.创建 GoLang之channel底层的数据结构是什么.channel的创建(2) 1.数据结 ...

  7. Golang关于channel死锁情况的汇总以及解决方案

    直接读取空channel的死锁 当一个channel中没有数据,而直接读取时,会发生死锁: func main() {q := make(chan int, 2)<-q } 错误提示: fata ...

  8. Golang笔记——channel(管道)

    推荐首先阅读:Golang笔记--goroutine(协程) 为什么需要 channel 前面使用全局变量加锁同步来解决 goroutine 的通讯,但不完美 主线程在等待所有 goroutine 全 ...

  9. golang的Channel初始化的有缓存与无缓存解释

    首先编程的时候遇到疑问,输出跟我所想预想不一样,后来查到了golang社区的帖子,其中一篇帖子 :健哥大人  做出了一些解释. 我摘抄重点过来: 无缓冲的与有缓冲channel有着重大差别,那就是一个 ...

最新文章

  1. Java8 Lamdba表达式 002
  2. php this 代表什么,php中$this-是什么意义
  3. 2020-11-18(失败的一天)
  4. 微信小程序开发系列五:微信小程序中如何响应用户输入事件
  5. GC:垃圾回收机制及算法
  6. Windows域控去掉密码强度策略 可以设置简单密码【全域策略生效】
  7. C语言 — 运算符的优先级与结合性
  8. c 程序设计语言简单列子,C语言程序设计实例大全(220个例子)
  9. Windows重新生成UEFI引导,解决Windows蓝屏\BCD 0xc0000098
  10. 2019最新《网易云课堂C++开发工程师案例-网吧收银系统(MFC+ADO)》
  11. uni-app项目利用HBuilder X工具使用命令一键自动编译导出APP资源
  12. 观点 | 量子卫星很近,但“无法破解”的网络可能还很远
  13. Collecting package metadata (current_repodata.json): failed(解决方案)
  14. NX二次开发-UFUN创建圆柱UF_MODL_create_cyl1
  15. 测开学习篇-html
  16. 放射组学常用到的一些工具(软件)
  17. 嵌入式编程中volatile的重要性
  18. PHP面向对象技术(全面讲解)(高洛峰)
  19. 指向结构体的指针和指向结构体指针的指针
  20. 如何评估工时和开发计划

热门文章

  1. Flink 累加器Accumulator
  2. Linux文件解hgc,Linux从实模式到保护模式.pdf
  3. 8寸7寸触摸屏常见的故障问题和维修方法分别是什么?
  4. 教你文件重命名快速操作
  5. 51单片机之 LCD1602液晶显示屏
  6. Lync 2010升级到Lync 2013之更新CU2!
  7. 程序员该如何把 Windows 系统打造的跟 Mac 一样牛逼?
  8. 年终盘点一 | 云原生的 2022 年:降本提效、全面 Serverless 化
  9. 截取含有中文、Emoji表情、特殊符号的字符串
  10. 微信支付(一)SpringBoot 实现微信扫码支付/Native支付