• 从两道题看go channel的用法


    在知乎看到有人分享了几道笔试题,自己总结了一下其中与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/

  • 相关阅读:
    Nginx作为缓存服务
    Nginx作为代理服务
    ZipUtils zip压缩实现
    getman九桃小说解析油猴脚本
    maven添加代理加速jar包下载
    ffmpeg MP3 flv 视频转mp3
    ActiveMQ配置用户认证信息
    JS实现HTML标签转义及反转义
    删除registry镜像数据,以centos为例
    启动一个带登录账号密码的registry容器
  • 原文地址:https://www.cnblogs.com/DilonWu/p/8677231.html
Copyright © 2020-2023  润新知