前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >runtime.Goexit 的使用

runtime.Goexit 的使用

作者头像
李海彬
发布2018-10-08 14:47:31
1.4K0
发布2018-10-08 14:47:31
举报
文章被收录于专栏:Golang语言社区

原文作者:Rob Reid

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:

代码语言:javascript
复制
 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:

代码语言:javascript
复制
 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}
代码语言:javascript
复制
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:

代码语言:javascript
复制
1go run(5, "A")  
2run(1, "B")   
代码语言:javascript
复制
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.

代码语言:javascript
复制
 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}   
代码语言:javascript
复制
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:

代码语言:javascript
复制
 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}   
代码语言:javascript
复制
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:

代码语言:javascript
复制
 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}    
代码语言:javascript
复制
 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,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Run a goroutine synchronsly
  • sync.WaitGroup
  • Channels
  • runtime.Goexit()
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档