私はGoで書かれたサーバーを長時間実行しています。メインは、プログラムのロジックが実行されるいくつかのゴルーチンを起動します。その後、メインは何も役に立ちません。メインが終了すると、プログラムは終了します。プログラムを実行し続けるために私が今使用している方法は、fmt.Scanln()への単純な呼び出しです。他の人がどのようにメインから抜け出せないか知りたい以下は基本的な例です。ここで使用できるアイデアやベストプラクティスは何ですか?
チャネルを作成し、上記のチャネルで受信することでメインの終了を遅らせることを検討しましたが、ある時点ですべてのゴルーチンが非アクティブになると問題が発生する可能性があります。
サイドノート:私のサーバー(例ではありません)では、プログラムは実際にはシェルに接続して実行されていないので、とにかくコンソールと対話するのは意味がありません。今のところは動作しますが、あると仮定して、「正しい」方法を探しています。
package main
import (
"fmt"
"time"
)
func main() {
go forever()
//Keep this goroutine from exiting
//so that the program doesn't end.
//This is the focus of my question.
fmt.Scanln()
}
func forever() {
for ; ; {
//An example goroutine that might run
//indefinitely. In actual implementation
//it might block on a chanel receive instead
//of time.Sleep for example.
fmt.Printf("%v+\n", time.Now())
time.Sleep(time.Second)
}
}
Goのランタイムの現在の設計では、プログラマがゴルーチンを終了するタイミングとプログラムを終了するタイミングを検出する責任があると想定しています。プログラマは、ゴルーチンおよびプログラム全体の終了条件を計算する必要があります。プログラムは、 _os.Exit
_ を呼び出すか、main()
関数から戻ることにより、通常の方法で終了できます。
チャネルを作成し、main()
の終了を遅らせることは、そのチャネルですぐに受信することにより、main
の終了を防ぐ有効な方法です。ただし、プログラムをいつ終了するかを検出する問題は解決しません。
main()
関数がwait-for-all-goroutines-to-terminateループに入る前にゴルーチンの数を計算できない場合、main
関数が保持できるようにデルタを送信する必要があります飛行中のゴルーチンの数の追跡:
_// Receives the change in the number of goroutines
var goroutineDelta = make(chan int)
func main() {
go forever()
numGoroutines := 0
for diff := range goroutineDelta {
numGoroutines += diff
if numGoroutines == 0 { os.Exit(0) }
}
}
// Conceptual code
func forever() {
for {
if needToCreateANewGoroutine {
// Make sure to do this before "go f()", not within f()
goroutineDelta <- +1
go f()
}
}
}
func f() {
// When the termination condition for this goroutine is detected, do:
goroutineDelta <- -1
}
_
別の方法は、チャネルを _sync.WaitGroup
_ に置き換えることです。このアプローチの欠点は、wg.Add(int)
を呼び出す前にwg.Wait()
を呼び出す必要があるため、後続のgoroutinesができる間にmain()
に少なくとも1つのゴルーチンを作成する必要があることです。プログラムの任意の部分で作成される:
_var wg sync.WaitGroup
func main() {
// Create at least 1 goroutine
wg.Add(1)
go f()
go forever()
wg.Wait()
}
// Conceptual code
func forever() {
for {
if needToCreateANewGoroutine {
wg.Add(1)
go f()
}
}
}
func f() {
// When the termination condition for this goroutine is detected, do:
wg.Done()
}
_
永遠にブロックします。例えば、
package main
import (
"fmt"
"time"
)
func main() {
go forever()
select {} // block forever
}
func forever() {
for {
fmt.Printf("%v+\n", time.Now())
time.Sleep(time.Second)
}
}
Goの runtime パッケージには runtime.Goexit
それはまさにあなたが望むことをします。
メインゴルーチンからGoexitを呼び出すと、func mainが戻ることなく、そのゴルーチンが終了します。 func mainが返されないため、プログラムは他のゴルーチンの実行を継続します。他のすべてのゴルーチンが終了すると、プログラムはクラッシュします。
playground の例
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
time.Sleep(time.Second)
fmt.Println("Go 1")
}()
go func() {
time.Sleep(time.Second * 2)
fmt.Println("Go 2")
}()
runtime.Goexit()
fmt.Println("Exit")
}
これはチャンネルを使用した永遠のシンプルなブロックです
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan bool)
go forever()
<-done // Block forever
}
func forever() {
for {
fmt.Printf("%v+\n", time.Now())
time.Sleep(time.Second)
}
}
スーパーバイザを使用してプロセスをデーモン化できます( http://supervisord.org/ )。関数は永遠に実行されるプロセスであり、関数mainの一部を処理します。スーパーバイザ制御インターフェイスを使用して、プロセスを開始/シャットダウン/チェックします。