Go的并发的退出

2023-09-15 17:59:43

有时候我们需要通知goroutine停止它正在干的事情,比如一个正在执行计算的web服务,然而它的客户端已经断开了和服务端的连接。

Go语言并没有提供在一个goroutine中终止另一个goroutine的方法,由于这样会导致goroutine之间的共享变量落在未定义的状态上。在8.7节中的rocket launch程序中,我们往名字叫abort的channel里发送了一个简单的值,在countdown的goroutine中会把这个值理解为自己的退出信号。但是如果我们想要退出两个或者任意多个goroutine怎么办呢?

一种可能的手段是向abort的channel里发送和goroutine数目一样多的事件来退出它们。如果这些goroutine中已经有一些自己退出了,那么会导致我们的channel里的事件数比goroutine还多,这样导致我们的发送直接被阻塞。另一方面,如果这些goroutine又生成了其它的goroutine,我们的channel里的数目又太少了,所以有些goroutine可能会无法接收到退出消息。一般情况下我们是很难知道在某一个时刻具体有多少个goroutine在运行着的。另外,当一个goroutine从abort channel中接收到一个值的时候,他会消费掉这个值,这样其它的goroutine就没法看到这条信息。为了能够达到我们退出goroutine的目的,我们需要更靠谱的策略,来通过一个channel把消息广播出去,这样goroutine们能够看到这条事件消息,并且在事件完成之后,可以知道这件事已经发生过了。

回忆一下我们关闭了一个channel并且被消费掉了所有已发送的值,操作channel之后的代码可以立即被执行,并且会产生零值。我们可以将这个机制扩展一下,来作为我们的广播机制:不要向channel发送值,而是用关闭一个channel来进行广播。

只要一些小修改,我们就可以把退出逻辑加入到前一节的du程序。首先,我们创建一个退出的channel,不需要向这个channel发送任何值,但其所在的闭包内要写明程序需要退出。我们同时还定义了一个工具函数,cancelled,这个函数在被调用的时候会轮询退出状态。

gopl.io/ch8/du4

var done = make(chan struct{}) func cancelled() bool { select { case <-done: return true default: return false } }

下面我们创建一个从标准输入流中读取内容的goroutine,这是一个比较典型的连接到终端的程序。每当有输入被读到(比如用户按了回车键),这个goroutine就会把取消消息通过关闭done的channel广播出去。

// Cancel traversal when input is detected. go func() { os.Stdin.Read(make([]byte, 1)) // read a single byte close(done) }()

现在我们需要使我们的goroutine来对取消进行响应。在main goroutine中,我们添加了select的第三个case语句,尝试从done channel中接收内容。如果这个case被满足的话,在select到的时候即会返回,但在结束之前我们需要把fileSizes channel中的内容“排”空,在channel被关闭之前,舍弃掉所有值。这样可以保证对walkDir的调用不要被向fileSizes发送信息阻塞住,可以正确地完成。

for { select { case <-done: // Drain fileSizes to allow existing goroutines to finish. for range fileSizes { // Do nothing. } return case size, ok := <-fileSizes: // ... } }

walkDir这个goroutine一启动就会轮询取消状态,如果取消状态被设置的话会直接返回,并且不做额外的事情。这样我们将所有在取消事件之后创建的goroutine改变为无操作。

func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) { defer n.Done() if cancelled() { return } for _, entry := range dirents(dir) { // ... } }

在walkDir函数的循环中我们对取消状态进行轮询可以带来明显的益处,可以避免在取消事件发生时还去创建goroutine。取消本身是有一些代价的;想要快速的响应需要对程序逻辑进行侵入式的修改。确保在取消发生之后不要有代价太大的操作可能会需要修改你代码里的很多地方,但是在一些重要的地方去检查取消事件也确实能带来很大的好处。

对这个程序的一个简单的性能分析可以揭示瓶颈在dirents函数中获取一个信号量。下面的select可以让这种操作可以被取消,并且可以将取消时的延迟从几百毫秒降低到几十毫秒。

func dirents(dir string) []os.FileInfo { select { case sema <- struct{}{}: // acquire token case <-done: return nil // cancelled } defer func() { <-sema }() // release token // ...read directory... }

现在当取消发生时,所有后台的goroutine都会迅速停止并且主函数会返回。当然,当主函数返回时,一个程序会退出,而我们又无法在主函数退出的时候确认其已经释放了所有的资源(译注:因为程序都退出了,你的代码都没法执行了)。这里有一个方便的窍门我们可以一用:取代掉直接从主函数返回,我们调用一个panic,然后runtime会把每一个goroutine的栈dump下来。如果main goroutine是唯一一个剩下的goroutine的话,他会清理掉自己的一切资源。但是如果还有其它的goroutine没有退出,他们可能没办法被正确地取消掉,也有可能被取消但是取消操作会很花时间;所以这里的一个调研还是很有必要的。我们用panic来获取到足够的信息来验证我们上面的判断,看看最终到底是什么样的情况。

更多推荐

深度思考计算机网络面经之三

计算机网络.1说说你对tcp滑动窗口的理解TCP滑动窗口是TCP协议流量控制的一个重要机制。它的主要目的是确保发送方不会因为发送太多数据而使接收方不堪重负。下面我会详细地描述滑动窗口的概念:窗口的大小:滑动窗口的大小(以字节为单位)表示发送方在等待确认之前可以发送的最大数据量。该大小可以是固定的,但在现代的TCP实现中

【计算机基础知识】计算机的概念

欢迎来到我的:世界希望作者的文章对你有所帮助,有不足的地方还请指正,大家一起学习交流!目录前言1.计算机的概念计算机的发展历程知识拓展:计算机的特点计算机的分类2.计算机的应用计算机的发展趋势知识拓展:总结前言美国数学家香农说过:信息是能够消除不确定性的东西,也就是说信息能消除事物的不确定性,把不确定变成确定性;1.计

用户参与策略:商城小程序的营销推广

在现代数字化时代,商城小程序已成为许多企业推广产品和服务的重要工具。然而,要确保小程序的成功,不仅需要吸引用户访问,还需要采取有效的用户参与策略,以建立持久的客户关系。本文将深入探讨用户参与策略,以推广商城小程序并提高用户忠诚度。第一部分:用户参与的重要性建立品牌忠诚度用户参与是建立品牌忠诚度的关键。通过积极互动和参与

防火墙之firewall配置

firewall​CentOS7中防火墙已经由firewalld来管理,Centos7默认安装了firewalld。与iptables区别iptables仅能通过命令行进行配置;而firewalld提供了图形接口,类似windows防火墙的操作方式;iptables每一个单独更改意味着清除所有旧的规则,并从/etc/s

面试Java后端

sql五表联合查询面试八股JDK,JRE,JVM之间的区别JDK,Java标准开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等。JRE(JavaRuntimeEnvironment),Java运行环境,用于运行lava的字节码文件。JRE中包括

.bat批处理命令处理文件

批处理命令处理文件找到上级目录,并删除文件与文件夹参考资料:[BAT]如何获取bat的上一级目录、上两级目录..._bat上层目录_Risun_Lee的博客-CSDN博客@echooffsetcurrPath=%~dp0setparentPath=setparentparentPath=:beginfor/f"toke

华为云云耀云服务器L实例评测|部署功能强大的开源物联平台ThingsBoard

华为云云耀云服务器L实例评测|部署功能强大的开源物联平台ThingsBoard应用场景ThingsBoard介绍ThingBoard是一个开源的物联网(IoT)平台,旨在为智能设备、传感器和执行器的远程管理和控制提供一站式解决方案。它提供了一套功能强大的Web界面,允许用户监控和控制其物联网设备,同时支持设备之间的自定

开源在物联网(IoT)中的应用

🌷🍁博主猫头虎带您GotoNewWorld.✨🍁🦄博客首页——猫头虎的博客🎐🐳《面试题大全专栏》文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》学会Golang语言,畅玩云原生,走遍大

【Docker】Docker持续集成与持续部署(四)

前言:Docker与持续集成/持续部署(CI/CD)的作用是通过容器化技术实现环境一致性、快速构建和隔离性,从而加快软件交付速度、提高部署效率,确保持续集成和持续部署的顺利实施。持续集成(ContinuousIntegration)持续集成是一种开发实践,通过频繁地将代码集成到共享的主干(版本控制仓库)中,然后自动运行

ElasticSearch系列-索引原理与数据读写流程详解

索引原理倒排索引倒排索引(InvertedIndex)也叫反向索引,有反向索引必有正向索引。通俗地来讲,正向索引是通过key找value,反向索引则是通过value找key。ES底层在检索时底层使用的就是倒排索引。索引模型现有索引和映射如下:{"products":{"mappings":{"properties":{

C语言指针笔试题讲解

大家好,我们来学习一些C语言的指针笔试题。对于C语言指针的模块想必大家都非常的头疼吧,那么我们就来就来看看一些关于C语言指针的笔试题。首先让我们看到我们今天的第一题。intmain(){inta[5]={1,2,3,4,5};int*ptr=(int*)(&a+1);printf("%d,%d",*(a+1),*(pt

热文推荐