从两道题看go channel的用法
程序员文章站
2022-06-26 11:17:12
在知乎看到有人分享了几道笔试题,自己总结了一下其中与channel有关的题目。全部题目在这里: https://zhuanlan.zhihu.com/p/35058068 题目 5、请找出下面代码的问题所在。 go func main() { abc := make(chan int, 1000) ......
在知乎看到有人分享了几道笔试题,自己总结了一下其中与channel有关的题目。全部题目在这里:
题目
5、请找出下面代码的问题所在。
func main() { ch := make(chan int, 1000) // goroutine1 go func() { for i := 0; i < 10; i++ { ch <- i } }() // goroutine2 go func() { for { a, ok := <-ch if !ok { fmt.Println("close") return } fmt.Println("a: ", a) } }() close(ch) fmt.Println("ok") time.Sleep(time.Second * 100) }
8、请说出下面代码哪里写错了
func main() { abc := make(chan int, 1000) for i := 0; i < 10; i++ { abc <- i } // receiver goroutine go func() { for { a := <-abc fmt.Println("a: ", a) } }() close(abc) fmt.Println("close") time.Sleep(time.Second * 100) }
解释
首先要明确这两个要点:
- 向一个已经关闭的channel发送数据是会抛panic的,但是从一个已关闭的channel接收数据并不会抛panic,而是得到channel数据类型的零值。
- 同一个段程序每次启动后goruntine的调度不一定相同,所以goroutine的执行顺序有可能不一样。
先看第一题,代码运行时会抛出panic:
panic: send on closed channel
这是因为goroutine1 还没把i 发送给ch,ch 就在main函数中被close,当把i 发送到ch 时就抛出了panic。同理goroutine2 中从已关闭的ch 中接收数据时ok 会返回false,然后就return了。而且由于goroutine 的执行顺序不一样,输出close、ok、panic的顺序也会不一样,有兴趣的话可以自己跑几次。
那么如果想让程序正常输出应该怎么改呢,下面是其中一种方法:
func main() { ch := make(chan int, 1000) // goroutine1 go func() { for i := 0; i < 10; i++ { ch <- i } // 只有一个goroutine向ch发送数据,可以考虑在这个goroutine中关闭。 close(ch) }() // goroutine2 go func() { for { a, ok := <-ch if !ok { fmt.Println("close") return } fmt.Println("a: ", a) } }() fmt.Println("ok") time.Sleep(time.Second * 100) }
再来看第二题,运行时输出完0—9后会一直输出0,直到sleep的时间结束。这是因为在把0—9发送给abc 后abc 就被关闭了,receiver goroutine 在接收完0—9后没有跳出for 循环,而是一直从被关闭的abc 中接收数据,所以接收到的是abc 的零值——0。
有一种最简单的办法可以改正这个程序:
func main() { abc := make(chan int, 1000) for i := 0; i < 10; i++ { abc <- i } go func() { // 使用for-range,abc被关闭后会自动退出for循环。 for a := range abc { fmt.Println("a: ", a) } }() close(abc) fmt.Println("close") time.Sleep(time.Second * 10) }
总结
使用channel时,
-
如果只有一个goroutine 向channel发送数据,可以在该goroutine 中close channel。当有多个goroutine 向channel 发送元素时close channel 的方法可以参考这篇文章:
用for-range 从channel 中接收数据代码简洁而且基本没有副作用。
扩展阅读
关于goroutine是如何调度的,可以参考这篇文章: