runtime.Goexit 的使用

简介:

If you've ever needed to kick off multiple goroutines from func main, you'd have probably noticed that the main goroutine isn't likely to hang around long enough for the other goroutines to finish:

1package main
 2
 3import (  
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {  
 9    go run(1, "A")
10    go run(5, "B")
11}
12
13func run(iter int, name string) {  
14    for i := 0; i < iter; i++ {
15        time.Sleep(time.Second)
16        fmt.Println(name)
17    }
18}

It'll come as no surprise that this program outputs nothing and exits with an exit code of 0. The nature of goroutines is to be asynchronous, so while the "A" and "B" goroutines are being scheduled, the main goroutine is running to completion and hence closing our application.

There are many ways to run both the "A" and "B" goroutines to completion, some more involved than others. Here are a few:

Run a goroutine synchronsly
If you're confident that one of your goroutines will run for longer than the other, you could simply call one of the routines synchronously and hope for the best:

1package main
 2
 3import (  
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {  
 9    go run(1, "A")
10    run(5, "B")
11}
12
13func run(iter int, name string) {  
14    for i := 0; i < iter; i++ {
15        time.Sleep(time.Second)
16        fmt.Println(name)
17    }
18}



1$ go run main.go
2B  
3A  
4B  
5B  
6B  
7B  
8<EXIT 0>

This of course falls down if the goroutine you're waiting on takes less time than the other, as the only thing keeping your application running is the goroutine you're running synchronously:

1go run(5, "A")  
2run(1, "B")   


1$ go run main.go
2B  
3<EXIT 0>

...so not a workable solution unless you're running things like long-running web servers.

sync.WaitGroup
A more elegant solution would be to use sync.WaitGroup configured with a delta equal to the number of goroutines you're spawning. Your application will run to completion after all of the goroutines exit.

In the following example, I'm assuming that we don't have access to the runfunction and so am dealing with the sync.WaitGroup internally to the mainfunction.

1package main
 2
 3import (  
 4    "fmt"
 5    "sync"
 6    "time"
 7)
 8
 9func main() {  
10    var wg sync.WaitGroup
11    wg.Add(2)
12    go func() {
13        defer wg.Done()
14        run(1, "A")
15    }()
16    go func() {
17        defer wg.Done()
18        run(5, "B")
19    }()
20    wg.Wait()
21}
22
23func run(iter int, name string) {  
24    for i := 0; i < iter; i++ {
25        time.Sleep(time.Second)
26        fmt.Println(name)
27    }
28}   


1$ go run main.go
2B  
3A  
4B  
5B  
6B  
7B  
8<EXIT 0>

This is a more elegant solution to the hit-and-hope solution as it leaves nothing to chance. As with the above example, you'll likely want/need to keep the wait group code within your main function, so provided you don't mind polluting it with synchronisation code, you're all good.

If you need to add/remove a goroutine, don't forget to increment the delta, or your application won't behave as expected!

Channels
It's also possible to use channels to acheive this behaviour, by creating a buffered channel with the same size as the delta you initialised the sync.WaitGroup with.

In the below example, I once again assume no access to the run function and keep all synchronisation logic in the main function:

1package main
 2
 3import (  
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {  
 9    done := make(chan struct{})
10
11    go func() {
12        defer func() { done <- struct{}{} }()
13        run(1, "A")
14    }()
15
16    go func() {
17        defer func() { done <- struct{}{} }()
18        run(5, "B")
19    }()
20
21    for i := 0; i < 2; i++ {
22        <-done
23    }
24}
25
26func run(iter int, name string) {  
27    for i := 0; i < iter; i++ {
28        time.Sleep(time.Second)
29        fmt.Println(name)
30    }
31}   


1$ go run main.go
2B  
3A  
4B  
5B  
6B  
7B

The obvious added complexity and the fact that the synchronisation code needs to be updated if a goroutine needs to be added/removed detract from the elegance of this approach. Forget to increment your channel's reader delta and your application will exit earlier than expected and forget to decrement it and it'll crash with a deadlock.

runtime.Goexit()
Another solution is to use the runtime package's Goexit function. This function executes all deferred statements and then stops the calling goroutine, leaving all other goroutines running. Like all other goroutines, Goexit can be called from the main goroutine to kill it and allow other goroutines to continue running.

Exit wise, once the Goexit call is in place, your application can only fail. If your application is running in an orchestrated environment like Kubernetes (or you're just happy to tolerate non-zero exit codes), this might be absolutely fine but it's something to be aware of.

There are two ways your application can now exit (both resulting in an exit code of 2):

  • If all of the other goroutines run to completion, there'll be no more goroutines to schedule and so the runtime scheduler will panic with a deadlock informing you that Goexit was called and that there are no more goroutines.
  • If any of the other goroutines panic, the application will crash as if any other unrecovered panic had occurred.

With all the doom and gloom out the way, let's take a look at the code:

1package main
 2
 3import (  
 4    "fmt"
 5    "runtime"
 6    "time"
 7)
 8
 9func main() {  
10    go run(1, "A")
11    go run(5, "B")
12
13    runtime.Goexit()
14}
15
16func run(iter int, name string) {  
17    for i := 0; i < iter; i++ {
18        time.Sleep(time.Second)
19        fmt.Println(name)
20    }
21}
1$ go run main.go
 2B  
 3A  
 4B  
 5B  
 6B  
 7B  
 8fatal error: no goroutines (main called runtime.Goexit) - deadlock!  
 9<STACK OMITTED>  
10<EXIT 2>

Succinct, if a little scary!

This solution understandably won't be for everyone, especially if you're working with inexperienced gophers (for reasons of sheer confusion, "my application keeps failing" and "nice, I'll use this everywhere") but it's nevertheless an interesting one, if only from an academic perspective.

原文发布时间为:2018-08-22
本文来自云栖社区合作伙伴“Golang语言社区”,了解相关信息可以关注“Golang语言社区”。

相关文章
|
4天前
|
人工智能 弹性计算 运维
|
2天前
|
缓存 人工智能 安全
GPT-5.6 Terra与GPT-5.5性能实测:成本减半后的跑分对比与快速迁移指南
GPT-5.6 Terra 的定价为每百万 token 输入 2.50/输出 15。GPT-5.5 则是 5/ 30。Terra 的每一项费率,包括 $0.25/M 的缓存读取,都恰好是 GPT-5.5 的一半,因此在任何工作负载组合下,Terra 都固定 便宜 2.0x。以每天 10 万次请求、3K token 提示词计算,大约是 Terra 每天 2,000,GPT−5.5每天 4,000,即每月约 60,000对 120,000。问题在于:OpenAI 没有发布任何针对 Terra 的编码基准。那个著名的 91.9% Terminal-Bench 数字是 Sol 在 Ul
|
1天前
|
SQL 人工智能 自然语言处理
大模型内容安全实时防护:恶意Prompt注入拦截、越权阻断与熔断机制方案.166
本文系统阐述大模型输入安全防护体系,涵盖提示词注入、恶意Prompt拦截、越权阻断与输入熔断四大核心风险及应对方案。提出四层防护架构(预处理、检测、鉴权、熔断),结合规则引擎、语义识别与RBAC权限控制,实现全链路实时防护,保障业务合规、数据安全与服务稳定。
203 1
|
25天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)
|
9天前
|
人工智能 编解码 物联网
2026 最新Stable Diffusion 本地部署教程 下载安装使用详细图解(含官网安装包)
Stable Diffusion(SD)是2022年发布的开源文生图模型,由Stability AI等联合开发。支持文生图、图生图、局部重绘等,依托VAE降低算力需求,可在消费级显卡运行。本文提供秋葉aaaki制作的Windows整合包(含图形界面与插件),开箱即用,零配置启动。
|
10天前
|
人工智能 缓存 安全
Claude Code 封号真实原因曝光,这次彻底不装了,直接针对国内开发者的账号下手?
Claude Code 封号潮背后:逆向扒出客户端隐写区域标记,Anthropic 政策收紧叠加 DeepSeek 7 月涨价,国产替代更紧迫。
|
19天前
|
存储 人工智能 监控
QoderWork完全指南:从入门到精通,把“AI实习生”变成你的全能工作搭档
阿里云2026年推出的桌面端AI工作助手QoderWork,不止聊天,更可动手干活:本地运行、安全可控,支持文件整理、数据分析、PPT生成、网页开发等;内置专家套件、多Agent协作与自定义Skills,让AI真正成为你身边的“AI实习生”。
|
16天前
|
人工智能 JSON 自然语言处理
让教学更智慧:用阿里云百炼工作流,自动生成中小学教材内容#小有可为#有温度的AI
通过可视化工作流编排,将大模型推理能力转化为标准化的教学内容生成引擎。教师只需输入教材标题和适用学段,即可自动获得结构完整、符合课程标准的章节内容,大幅降低备课门槛,助力教育资源均衡化。
505 127
|
11天前
|
人工智能 安全 程序员
终于,Claude Code 封号的原因被曝光了!竟然针对中国用户,植入隐形代码?!
通俗易懂地揭秘 Claude Code 封号的手段,分享一些自己对 AI 编程困境的思考,Codex、Cursor、DeepSeek、智谱 GLM、甚至是豆包,都有所行动了
529 1

热门文章

最新文章