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语言社区”。

相关文章
|
8天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1929 6
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
2天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
498 111
|
6天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
678 111
|
16天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2598 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
14天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
1820 2
|
2天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
|
16天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max-Preview深度全解析:2.4万亿参数旗舰MoE模型+Token Plan限时优惠完整落地指南
2026年7月,全新旗舰级混合专家大模型Qwen3.8-Max-Preview正式开放抢先体验,作为通义千问Qwen3系列规格最高、综合推理能力顶尖的新一代模型,该模型总参数量达到2.4万亿(2.4T),是当前线上可调用的原生多模态旗舰模型,综合推理水准对标海外顶级Fable 5模型,在复杂工程开发、长文档深度分析、多步骤智能体自治、跨境多语言创作、海量数据挖掘五大高难度业务场景实现跨越式性能提升。
1450 2
|
3天前
Qoder 一周年 × Qwen3.8-Max 正式上线,多重好礼限时领
8月3日,Qwen3.8-Max 正式上线Qoder,迎来Qoder一周年。新老用户可领800次免费调用,下单再赠2000次;夜间(22:00–08:00)调用5折;邀请好友双方得积分与调用额度。
287 0