序文
runtime
packageには Goexit()
というfunctionがあります。
// Goexit terminates the goroutine that calls it. No other goroutine is affected. // Goexit runs all deferred calls before terminating the goroutine. Because Goexit // is not a panic, any recover calls in those deferred functions will return nil. // // Calling Goexit from the main goroutine terminates that goroutine // without func main returning. Since func main has not returned, // the program continues execution of other goroutines. // If all other goroutines exit, the program crashes. // Goexitはそれを呼び出すgoroutineを終了します。 その他のゴルーチンは影響を受けません。 // Goexitは、goroutineを終了する前にすべてのdefer呼び出しを実行します。 Goexitはpanicではないので、これらのdefer関数の中のすべてのrecover呼び出しはnilを返します。 // メインのgoroutineからGoexitを呼び出すと、メインgoroutineに戻ることなくそのgoroutineが終了します。 func mainは返されていないので、プログラムは他のgoroutineの実行を続けます。 // その他のゴルーチンがすべて終了すると、プログラムがクラッシュします。 func Goexit() { // Run all deferred functions for the current goroutine. // This code is similar to gopanic, see that implementation // for detailed comments. // 現在のゴルーチンのすべてのdefer関数を実行します。 // このコードはgopanicに似ています。詳細なコメントの実装を参照してください。 gp := getg() for { d := gp._defer if d == nil { break } if d.started { if d._panic != nil { d._panic.aborted = true d._panic = nil } d.fn = nil gp._defer = d.link freedefer(d) continue } d.started = true reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) if gp._defer != d { throw("bad defer entry in Goexit") } d._panic = nil d.fn = nil gp._defer = d.link freedefer(d) // Note: we ignore recovers here because Goexit isn't a panic } goexit1() }
コメントにあるように、以下の特性を持ちます。
- 呼び出し元のgoroutineを終了する
- すべてのgoroutineが終了すると、
deadlock
を起こしクラッシュする - panic処理と違い、defer function内のrecoverはnilを返す
なかなか使い所の難しい要素ではありますが、Goでは FailNow()
などで使われたりしています。
さて、このGoexitですがそこまで使われていないっぽくて、自分自身使ってみたことがないので試してみました。
試行
それでは軽く試してみましょう。
package main import ( "fmt" "runtime" "time" ) func main() { go func() { defer fmt.Println("Done") time.Sleep(1 * time.Second) runtime.Goexit() }() fmt.Printf("goroutines: %v\n", runtime.NumGoroutine()) time.Sleep(2 * time.Second) fmt.Printf("goroutines: %v\n", runtime.NumGoroutine()) } // goroutines: 2 // Done // goroutines: 1
正常に終了し、goroutineの数が減っていることがわかりますね。
ただ、これだけでは単純に return
するのと変わりません。次は少し捻ってみましょう。
package main import ( "fmt" "runtime" "time" ) func main() { go func() { defer fmt.Println("Done") HelloAndExit() }() time.Sleep(1 * time.Second) } func HelloAndExit() { fmt.Println("Hello") runtime.Goexit() } // Hello // Done
Hello
と出力してからgoroutineを終了させるパターンです。
ただし、これだけだと
func HelloAndExit() { fmt.Println("Hello") }
としても同じ結果になります。
それでは、 return
との違いを試してみましょう。
package main import ( "fmt" "runtime" "time" ) func main() { go func() { defer fmt.Println("Done HelloAndExit goroutine") fmt.Println("Start HelloAndExit goroutine") HelloAndExit() HelloAndExit() }() go func() { defer fmt.Println("Done HelloAndReturn goroutine") fmt.Println("Start HelloAndReturn goroutine") HelloAndReturn() HelloAndReturn() }() time.Sleep(1 * time.Second) } func HelloAndExit() { fmt.Println("HelloAndExit") runtime.Goexit() } func HelloAndReturn() { fmt.Println("HelloAndReturn") return } // Start HelloAndReturn goroutine // HelloAndReturn // HelloAndReturn // Done HelloAndReturn goroutine // Start HelloAndExit goroutine // HelloAndExit // Done HelloAndExit goroutine
このように、 return
の場合では後続処理が走り、 runtime.Goexit
では実行された時点で呼び出し元goroutineが終了していることが分かりますね。
こんな風にネストを深くしても、
package main import ( "fmt" "runtime" "time" ) func main() { go func() { defer fmt.Println("Done") Wrapper() }() time.Sleep(2 * time.Second) } func Wrapper() { fmt.Println("Wrapper") HelloAndExit() time.Sleep(1 * time.Second) fmt.Println("Wake up") } func HelloAndExit() { fmt.Println("HelloAndExit") runtime.Goexit() } // Wrapper // HelloAndExit // Done
runtime.Goexit
が呼び出された時点で、Wrapperの処理を打ち切ってgoroutineが終了されています。
まとめ
面白い機能なんですが、利用シーンを考えてみるとなかなか難しくて、「これだ!」と思うような実践的な実装が思いつきませんでした・・・
実装例を探そうとしても、そもそもGoexitを使っている人が居なかっt(ry
こんな感じで使うと良いよっていうのを、どなたかデキルGopherな方が提示してくれるのを待つしか無い・・・