Golang并发模型:并发协程的优雅退出_lessisbetter的博客-程序员秘密

技术标签: 并发  golang  goroutine  concurrency  

goroutine作为Golang并发的核心,我们不仅要关注它们的创建和管理,当然还要关注如何合理的退出这些协程,不(合理)退出不然可能会造成阻塞、panic、程序行为异常、数据结果不正确等问题。这篇文章介绍,如何合理的退出goroutine,减少软件bug。

goroutine在退出方面,不像线程和进程,不能通过某种手段强制关闭它们,只能等待goroutine主动退出。但也无需为退出、关闭goroutine而烦恼,下面就介绍3种优雅退出goroutine的方法,只要采用这种最佳实践去设计,基本上就可以确保goroutine退出上不会有问题,尽情享用。

1:使用for-range退出

for-range是使用频率很高的结构,常用它来遍历数据,range能够感知channel的关闭,当channel被发送数据的协程关闭时,range就会结束,接着退出for循环。

它在并发中的使用场景是:当协程只从1个channel读取数据,然后进行处理,处理后协程退出。下面这个示例程序,当in通道被关闭时,协程可自动退出。

go func(in <-chan int) {
    
    // Using for-range to exit goroutine
    // range has the ability to detect the close/end of a channel
    for x := range in {
    
        fmt.Printf("Process %d\n", x)
    }
}(inCh)

2:使用,ok退出

for-select也是使用频率很高的结构,select提供了多路复用的能力,所以for-select可以让函数具有持续多路处理多个channel的能力。但select没有感知channel的关闭,这引出了2个问题

  1. 继续在关闭的通道上读,会读到通道传输数据类型的零值,如果是指针类型,读到nil,继续处理还会产生nil。
  2. 继续在关闭的通道上写,将会panic。

问题2可以这样解决,通道只由发送方关闭,接收方不可关闭,即某个写通道只由使用该select的协程关闭,select中就不存在继续在关闭的通道上写数据的问题。

问题1可以使用,ok来检测通道的关闭,使用情况有2种。

第一种:如果某个通道关闭后,需要退出协程,直接return即可。示例代码中,该协程需要从in通道读数据,还需要定时打印已经处理的数量,有2件事要做,所有不能使用for-range,需要使用for-select,当in关闭时,ok=false,我们直接返回。

go func() {
    
	// in for-select using ok to exit goroutine
	for {
    
		select {
    
		case x, ok := <-in:
			if !ok {
    
				return
			}
			fmt.Printf("Process %d\n", x)
			processedCnt++
		case <-t.C:
			fmt.Printf("Working, processedCnt = %d\n", processedCnt)
		}
	}
}()

第二种:如果某个通道关闭了,不再处理该通道,而是继续处理其他case,退出是等待所有的可读通道关闭。我们需要使用select的一个特征:select不会在nil的通道上进行等待。这种情况,把只读通道设置为nil即可解决。

go func() {
    
	// in for-select using ok to exit goroutine
	for {
    
		select {
    
		case x, ok := <-in1:
			if !ok {
    
				in1 = nil
			}
			// Process
		case y, ok := <-in2:
			if !ok {
    
				in2 = nil
			}
			// Process
		case <-t.C:
			fmt.Printf("Working, processedCnt = %d\n", processedCnt)
		}

		// If both in channel are closed, goroutine exit
		if in1 == nil && in2 == nil {
    
			return
		}
	}
}()

3:使用退出通道退出

使用,ok来退出使用for-select协程,解决是当读入数据的通道关闭时,没数据读时程序的正常结束。想想下面这2种场景,,ok还能适用吗?

  1. 接收的协程要退出了,如果它直接退出,不告知发送协程,发送协程将阻塞。
  2. 启动了一个工作协程处理数据,如何通知它退出?

使用一个专门的通道,发送退出的信号,可以解决这类问题。以第2个场景为例,协程入参包含一个停止通道stopCh,当stopCh被关闭,case <-stopCh会执行,直接返回即可。

当我启动了100个worker时,只要main()执行关闭stopCh,每一个worker都会都到信号,进而关闭。如果main()向stopCh发送100个数据,这种就低效了。

func worker(stopCh <-chan struct{
    }) {
    
	go func() {
    
		defer fmt.Println("worker exit")
		// Using stop channel explicit exit
		for {
    
			select {
    
			case <-stopCh:
				fmt.Println("Recv stop signal")
				return
			case <-t.C:
				fmt.Println("Working .")
			}
		}
	}()
	return
}

最佳实践回顾

  1. 发送协程主动关闭通道,接收协程不关闭通道。技巧:把接收方的通道入参声明为只读,如果接收协程关闭只读协程,编译时就会报错。
  2. 协程处理1个通道,并且是读时,协程优先使用for-range,因为range可以关闭通道的关闭自动退出协程。
  3. ,ok可以处理多个读通道关闭,需要关闭当前使用for-select的协程。
  4. 显式关闭通道stopCh可以处理主动通知协程退出的场景。

完整示例代码

本文所有代码都在仓库,可查看完整示例代码:https://github.com/Shitaibin/golang_goroutine_exit

并发系列文章推荐

  1. 如果这篇文章对你有帮助,不妨关注下我的Github,有文章会收到通知。
  2. 本文作者:大彬
  3. 如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2018/12/02/golang-exit-goroutine-in-3-ways/
关注公众号,获取最新Golang文章。
一起学Golang-分享有料的Go语言技术
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_43499523/article/details/85850178

智能推荐

Python笔记(四)网络编程_aishachi2029的博客-程序员秘密

一、IP地址IP地址用来标记网络上的一台电脑,在本地局域网上是唯一的;Linux中用ifconfig命令来查看IP地址,Windows中用ipconfig命令;禁用网卡:sudo ifconfig ens33 down开启网卡:sudo ifconfig ens33 upIP地址的分类:IPV4、IPV6每个IP地址由两个部分组成:网络地址、主...

process information unavailable_在下蔡江琪的博客-程序员秘密

今天在虚拟机kill进程的时候 出现了process information unavailable出现这个问题的原因呢?其实很简单,就是你用A用户创建了一个进程,然后你用B用户把他干掉了,虽然是干掉了,但是查看的时候会出现这个该怎么解决呢?首先我们进入到 tmp目录下,用ls查看,你会发现有几个以hsperfdata_开头的文件然后你点进去看的时候就知道那个进程是哪个用...

linux下c语言编程gedit,把gedit打造成强大的C/C++语言IDE_统一老师的博客-程序员秘密

Linux下编程环境,除了vim,emacs这等强大编辑器,其实gedit也是不错的选择,特别是轻巧易用,不像前二者那么难以驾驭。gedit可扩展性很强,我们可以通过给gedit添加插件,让gedit成为一个强大的C语言IDE。今天心血来潮,配置了几个gedit插件,现在编C/C++程序基本和windows下VC差不多了。======================================...

PopupWindow的使用_powerx_yc的博客-程序员秘密

在Android设计弹出框是一个很常见的交互方式。弹出框的具体实现方式主要有两种一种是Dialog,另一种是PopupWindow。这两者的主要区别是Dialog的显示的位置是固定,而PopupWindow的位置是任意的。关于PopupWindow的官方描述如下:This class represents a popup window that can be used to display a...

网络填坑之路(6)ethtool - 命令管理以太网卡_muzlei的博客-程序员秘密

来源:如何使用 ethtool 命令管理以太网卡作者:Magesh Maruthamuthu译者:Xingyu.Wangethtool介绍在配置和显示以太网设备统计数据方面,ethtool提供了与mii-tool相似的性能。不过ethtool更加强大,包括了更多的配置和设备统计信息。ethtool 用于查看和修改网络设备(尤其是有线以太网设备)的驱动参数和硬件设置。你可以根据需...

随便推点

程序员如何爱护眼睛_JAVA叶知秋的博客-程序员秘密

作为一个程序员,一直奋斗在编码一线。从毕业那会拿着简历去深圳人才市场找工作,到跟项目经理做各种项目,最后到自己主导项目开发的全部过程,从未有离开编码一线。在中国,不能完全说编码没有前途,企业不景气时,最后被裁的一般是程序员,这说明程序员的职业压力要小于市场销售,另一方面,做自己感兴趣的事,做自己能力范围之内的事,发挥自己的应有能力,应该是职业规划的主要方向。每天和计算机打交道时间比较长,但是这...

如何制作网页-初学者入门HTML+CSS_网页制作_IT芷君的博客-程序员秘密

那么先用Dreamweaver ,Frontpage做,然后细节用Homesite,Hotdog修改,图片用Photoshop动画用fireworks ,Adobe ImageReady ,Flash然后申请空间,域名,大多数都是收费的,最后用软件上传就成了。5.使用面板组“资源”面板(如下图):点按钮,展开根目录的图片文件夹,选定该文件,用鼠标拖动至工作区合适位置。在很多时候,我们都需要html的静态页面代码,那么我们介绍一下静态页面的代码。构思好这个网页的结构,我们就可以开始制作了。......

体验古迹不用跑一趟 Google推出VR重现古代遗迹3D影像_weixin_34365635的博客-程序员秘密

非营利组织CyArk与Google合作开放遗产计划,利用3D技术重现历史古迹的表面色泽、纹理,并以雷射扫瞄器捕捉、毫米级精准度的几何构型,为古代遗址建立数字影像,可在计算机、手机或VR头戴式装置浏览。Google与保存文明古迹的非营利组织CyArk合作,将以虚拟现实(Virtual Reality, VR)技术重现可能因天灾、人祸破坏的人类历史遗迹。创立于2003年的CyArk是全世界第一个...

oracle 11g em 乱码_踏雪|无痕的博客-程序员秘密

方法1:Internet-->常规-->语言,添加“英语(美国) [en_us]”,并将其移动到第一个,再访问em登陆界面,即可正常(英文)。方法2:em的乱码是由java引起的,em的按钮是由java生成的图,oracle 11g使用的是jdk1.5,jdk1.5以后有了一个简便方法来解决这个问题。$ mkdir $ORACLE_HOME/jdk/jre/lib/font

PHP ecshop_JefferyC2018的博客-程序员秘密

前两年,居然收藏了一个ecshop的链接http://www.shopex.cn/

Markdown支持高亮的程序语言_不入流的IT宅男的博客-程序员秘密

Markdown支持高亮的语言Markdown插件代码时,使用 ```key 代码段 ``` .其中key是语言标记,使对应语言的关键字高亮。支持的语言如下Languagekey1C1cActionScriptactionscrip...

推荐文章

热门文章

相关标签