序文
runtime
packageには Goexit()
というfunctionがあります。
func Goexit() {
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)
}
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())
}
正常に終了し、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
と出力してから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
}
このように、 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()
}
runtime.Goexit
が呼び出された時点で、Wrapperの処理を打ち切ってgoroutineが終了されています。
まとめ
面白い機能なんですが、利用シーンを考えてみるとなかなか難しくて、「これだ!」と思うような実践的な実装が思いつきませんでした・・・
実装例を探そうとしても、そもそもGoexitを使っている人が居なかっt(ry
こんな感じで使うと良いよっていうのを、どなたかデキルGopherな方が提示してくれるのを待つしか無い・・・