【Go语言最佳实践:永远不要启动一个停止不了的 goroutine】

前面的例子显示当一个任务时没有必要时使用 goroutine。但使用 Go 语言的原因之一是该语言提供的并发功能。实际上,很多情况下你希望利用硬件中可用的并行性。为此,你必须使用 goroutines

这个简单的应用程序在两个不同的端口上提供 http 服务,端口 8080 用于应用程序服务,端口 8001 用于访问 /debug/pprof 终端。

package mainimport (
"fmt"
"net/http"
_ "net/http/pprof"
)func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintln(resp, "Hello, QCon!")
})
go http.ListenAndServe("127.0.0.1:8001", http.DefaultServeMux) // debug
http.ListenAndServe("0.0.0.0:8080", mux) // app traffic
}

虽然这个程序不是很复杂,但它代表了真实应用程序的基础。

该应用程序存在一些问题,因为它随着应用程序的增长而显露出来,所以我们现在来解决其中的一些问题。

func serveApp() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintln(resp, "Hello, QCon!")
})
http.ListenAndServe("0.0.0.0:8080", mux)
}func serveDebug() {
http.ListenAndServe("127.0.0.1:8001", http.DefaultServeMux)
}func main() {
go serveDebug()
serveApp()
}

通过将 serveAppserveDebug 处理程序分解成为它们自己的函数,我们将它们与 main.main 分离。 也遵循了上面的建议,并确保 serveAppserveDebug 将它们的并发性留给调用者。

但是这个程序存在一些可操作性问题。 如果 serveApp 返回,那么 main.main 将返回,导致程序关闭并由你使用的进程管理器来重新启动。

贴士:
正如 Go 语言中的函数将并发性留给调用者一样,应用程序应该将监视其状态和检测是否重启的工作留给另外的程序来做。 不要让你的应用程序负责重新启动自己,最好从应用程序外部处理该过程。

然而,serveDebug 是在一个单独的 goroutine 中运行的,返回后该 goroutine 将退出,而程序的其余部分继续。 由于 /debug 处理程序已停止工作很久,因此操作人员不会很高兴发现他们无法在你的应用程序中获取统计信息。

我们想要确保的是,如果任何负责提供此应用程序的 goroutine 停止,我们将关闭该应用程序。

func serveApp() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintln(resp, "Hello, QCon!")
})
if err := http.ListenAndServe("0.0.0.0:8080", mux); err != nil {
log.Fatal(err)
}
}func serveDebug() {
if err := http.ListenAndServe("127.0.0.1:8001", http.DefaultServeMux); err != nil {
log.Fatal(err)
}
}func main() {
go serveDebug()
go serveApp()
select {}
}

现在 serverAppserveDebug 检查从 ListenAndServe 返回的错误,并在需要时调用 log.Fatal。因为两个处理程序都在 goroutine 中运行,所以我们将 main goroutine 停在 select{} 中。

这种方法存在许多问题:

  1. 如果 ListenAndServer 返回 nil 错误,则不会调用 log.Fatal,并且该端口上的 HTTP 服务将在不停止应用程序的情况下关闭。
  2. log.Fatal 调用 os.Exit,它将无条件地退出程序; defer 不会被调用,其他 goroutines 也不会被通知关闭,程序就停止了。 这使得编写这些函数的测试变得困难。

贴士:
只在 main.maininit 函数中的使用 log.Fatal

我们真正想要的是任何错误发送回 goroutine 的调用者,以便它可以知道 goroutine 停止的原因,可以干净地关闭程序进程。

func serveApp() error {
mux := http.NewServeMux()
mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintln(resp, "Hello, QCon!")
})
return http.ListenAndServe("0.0.0.0:8080", mux)
}func serveDebug() error {
return http.ListenAndServe("127.0.0.1:8001", http.DefaultServeMux)
}func main() {
done := make(chan error, 2)
go func() {
done <- serveDebug()
}()
go func() {
done <- serveApp()
}()for i := 0; i < cap(done); i++ {
if err := <-done; err != nil {
fmt.Println("error: %v", err)
}
}
}

我们可以使用通道来收集 goroutine 的返回状态。通道的大小等于我们想要管理的 goroutine 的数量,这样发送到 done 通道就不会阻塞,因为这会阻止 goroutine 的关闭,导致它泄漏。

由于没有办法安全地关闭 done 通道,我们不能使用 for range 来循环通道直到获取所有 goroutine 发来的报告,而是循环我们开启的多个 goroutine,即通道的容量。

现在我们有办法等待每个 goroutine 干净地退出并记录他们遇到的错误。所需要的只是一种从第一个 goroutine 转发关闭信号到其他 goroutine 的方法。

事实证明,要求 http.Server 关闭是有点牵扯的,所以我将这个逻辑转给辅助函数。serve 助手使用一个地址和 http.Handler,类似于 http.ListenAndServe,还有一个 stop 通道,我们用它来触发 Shutdown 方法。

func serve(addr string, handler http.Handler, stop <-chan struct{}) error {
s := http.Server{
Addr: addr,
Handler: handler,
}go func() {
<-stop // wait for stop signal
s.Shutdown(context.Background())
}()return s.ListenAndServe()
}func serveApp(stop <-chan struct{}) error {
mux := http.NewServeMux()
mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintln(resp, "Hello, QCon!")
})
return serve("0.0.0.0:8080", mux, stop)
}func serveDebug(stop <-chan struct{}) error {
return serve("127.0.0.1:8001", http.DefaultServeMux, stop)
}func main() {
done := make(chan error, 2)
stop := make(chan struct{})
go func() {
done <- serveDebug(stop)
}()
go func() {
done <- serveApp(stop)
}()var stopped bool
for i := 0; i < cap(done); i++ {
if err := <-done; err != nil {
fmt.Println("error: %v", err)
}
if !stopped {
stopped = true
close(stop)
}
}
}

现在,每次我们在 done 通道上收到一个值时,我们关闭 stop 通道,这会导致在该通道上等待的所有 goroutine 关闭其 http.Server。 这反过来将导致其余所有的 ListenAndServe goroutines 返回。 一旦我们开启的所有 goroutine 都停止了,main.main 就会返回并且进程会干净地停止。

贴士:
自己编写这种逻辑是重复而微妙的。 参考下这个包: https://github.com/heptio/workgroup,它会为你完成大部分工作。

关注下方微信公众号,获取更多Go知识

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/692057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解锁文档处理新境界:ONLYOFFICE编辑功能为开发者带来新机遇

引言 ONLYOFFICE最新发布的文档8.0版本带来了一系列引人注目的功能和优化&#xff0c;为用户提供了更强大、更高效的在线编辑体验。这次更新涵盖了多个方面&#xff0c;包括PDF表单、RTL支持、单变量求解、图表向导以及插件界面设计更新等。这些新功能不仅提升了文档处理的便利…

day 22

时间获取: 1.time time_t time(time_t *tloc); 功能: 返回1970-1-1到现在的秒数&#xff08;格林威治时间&#xff09; 参数: tloc:存放秒数空间首地址 返回值: 成功返回秒数 失败返回-1 2.localtime struct …

职业资格高级执法考试试题及答案,分享几个实用搜题和学习工具 #知识分享#微信

作为当代大学生&#xff0c;我们常常面临着繁重的学业压力和众多的学习任务。在这个信息爆炸的时代&#xff0c;如何高效地进行搜题和学习成了我们迫切需要解决的问题。幸运的是&#xff0c;随着科技的不断进步&#xff0c;我们拥有了许多方便、实用的日常搜题和学习软件。 1.…

旗舰配置,巅峰性能 | 一文玩转铁威马 『F4-424 Pro』强大的Docker虚拟机功能【附产品开箱】

旗舰配置&#xff0c;巅峰性能 | 一文玩转铁威马 『F4-424 Pro』强大的Docker&虚拟机功能【附产品开箱】 哈喽小伙伴们&#xff0c;我是Stark-C~ 开篇 记得还在两个月之前&#xff0c;我为大家介绍了国产“开箱即用”的国民专业级NAS『铁威马』&#xff1a; &#x1f53…

idea代码review工具Code Review Helper使用介绍

之前在团队里面遇到一个关于代码review的问题&#xff0c;使用gitlab自己的还是facebook的Phabricator&#xff0c;很难看到整体逻辑&#xff0c;因为业务逻辑代码可能不在这次改动范围内&#xff0c;在去源库中找不好找。针对这个刚需&#xff0c;在网上找了一个idea的代码工具…

kali安装awvs报错error creating the database

修改host文件 vim /etc/hosts 加入 127.0.0.1 kali 重启再安装即可解决

软件实例分享,操作简单美发店会员登记记账本vip会员管理系统软件教程

软件实例分享&#xff0c;操作简单美发店会员登记记账本vip会员管理系统软件教程 一、前言 以下软件程序教程以 佳易王美发店会员管理系统软件V16为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、新会员可以直接使用手.机号&#xff0c;不需…

WordPress有没有必要选择付费主题

有必要。 能用付费的&#xff0c;就尽量别用免费的。 付费主题&#xff0c;情况也比较复杂&#xff0c;先讲一下付费主题的几种情况 1、是原创付费主题。是主题制作者原创的主题。 2、是把别人的主题二次开发的付费主题。这个有些是有原始开发者授权的&#xff0c;有些就是…

解libvirt中Domain类的方法

libvirt是一个用于管理虚拟化平台的开源工具包&#xff0c;提供了对不同虚拟化技术&#xff08;如KVM、QEMU、Xen等&#xff09;的统一管理接口。在libvirt中&#xff0c;Domain类表示虚拟机&#xff08;domain&#xff09;对象&#xff0c;并提供了丰富的方法来管理虚拟机的各…

Kernelized Correlation Filters KCF算法原理详解(阅读笔记)(待补充)

KCF 目录 KCF预备知识1. 岭回归2. 循环移位和循环矩阵3. 傅里叶对角化4. 方向梯度直方图&#xff08;HOG&#xff09; 正文1. 线性回归1.1. 岭回归1.2. 基于循环矩阵获取正负样本1.3. 基于傅里叶对角化的求解 2. 使用非线性回归对模型进行训练2.1. 应用kernel-trick的非线性模型…

如何使用IP代理解决亚马逊账号IP关联问题?

亚马逊账号IP关联问题是指当同一个IP地址下有多个亚马逊账号进行活动时&#xff0c;亚马逊会将它们关联在一起&#xff0c;从而可能导致账号被封禁或限制。 为了避免这种情况&#xff0c;许多人选择使用IP代理。 IP代理为什么可以解决亚马逊IP关联问题&#xff1f; IP代理是…

【LLVM】nsw和nuw的一个例子

nsw和nuw是LLVMIR提供给二元运算的flag。分别表示not signed wrap和not unsigned wrap。在LLVM2.6的更新日志中表述如下&#xff1a; The add, sub and mul instructions now support optional “nsw” and “nuw” bits which indicate that the operation is guaranteed to n…

欲速则不达,慢就是快!

引言 随着生活水平的提高&#xff0c;不少人的目标从原先的解决温饱转变为追求内心充实&#xff0c;但由于现在的时间过得越来越快以及其他外部因素&#xff0c;我们对很多东西的获取越来越没耐心&#xff0c;例如书店经常会看到《7天精通Java》、《3天掌握XXX》等等之类的书籍…

金山下的wps,无法删除水印

RT&#xff0c;正常删除水印的流程是&#xff0c;插入-水印-删除水印。 如图的操作方法不生效时&#xff0c;可用下面的方法 点击页眉或页脚&#xff0c;进入页眉的编辑模式&#xff0c;可以看到水印变成可以选中的状态&#xff0c;选中&#xff0c;点击delete或者鼠标右键删…

数衍科技连接CRM和营销系统,无代码API集成

{无代码开发的优势} 在数字化转型的浪潮中&#xff0c;无代码开发为企业带来了巨大的便利。数衍科技&#xff0c;这家专注于生态数据服务的国家高新技术企业&#xff0c;提供了一种无需依赖传统API开发的解决方案。企业可以通过其无代码开发平台&#xff0c;迅速构建数据连接&…

[AIGC] 垃圾回收算法

垃圾回收算法 垃圾回收是一种自动化的内存管理方法。其核心目标是找出程序中不再使用的动态分配的内存块&#xff0c;并将其标记为可重用&#xff0c;以便能够进行新的内存分配。 下面我们将介绍三种主要的垃圾回收算法&#xff1a;标记清除算法、复制算法和标记压缩算法。 …

如何使用Docker搭建YesPlayMusic网易云音乐播放器并发布至公网访问

文章目录 1. 安装Docker2. 本地安装部署YesPlayMusic3. 安装cpolar内网穿透4. 固定YesPlayMusic公网地址 本篇文章讲解如何使用Docker搭建YesPlayMusic网易云音乐播放器&#xff0c;并且结合cpolar内网穿透实现公网访问音乐播放器。 YesPlayMusic是一款优秀的个人音乐播放器&am…

知识蒸馏实战代码教学一(原理部分)

一、知识蒸馏的来源 知识蒸馏&#xff08;Knowledge Distillation&#xff09;源自于一篇由Hinton等人于2015年提出的论文《Distilling the Knowledge in a Neural Network》。这个方法旨在将一个大型、复杂的模型的知识&#xff08;通常称为教师模型&#xff09;转移到一个小型…

不同编程网站应当注意的点

文章目录 引入&#xff1a;洛谷&#xff1a;POJ&#xff1a;C语言&#xff1a;C: CF&#xff1a;个人建议&#xff1a;补充&#xff1a; 引入&#xff1a; 小伙伴们有没有遇到过这种情况&#xff1a;到一个新的网站去编程&#xff0c;思路、算法完全正确&#xff0c;提交上去却…

初识动态规划

斐波那契数列大家一定很熟悉吧**【f(n)f(n-1)f(n-2)】**&#xff0c;如果要通过代码来表达斐波那契数列也是很简单的&#xff0c;只需要一个简易的递归即可。但是由于递归的一些缺陷&#xff0c;自然有人会写出迭代方式 int Fun(int n){if(n1 || n2)return 1;first1;second1th…