欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

从两道题看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有关的题目。全部题目在这里:

https://zhuanlan.zhihu.com/p/35058068

题目

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 的方法可以参考这篇文章:

    https://www.jianshu.com/p/d24dfbb33781

  • 用for-range 从channel 中接收数据代码简洁而且基本没有副作用。

扩展阅读

关于goroutine是如何调度的,可以参考这篇文章:

https://blog.csdn.net/liangzhiyang/article/details/52669851

References

http://colobu.com/2016/04/14/Golang-Channels/