61

私はGoで書かれた長期稼働サーバーを持っています。Main は、プログラムのロジックが実行されるいくつかのゴルーチンを起動します。その後、メインは何も役に立ちません。main が終了すると、プログラムは終了します。プログラムを実行し続けるために私が現在使用している方法は、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)
    }
}
4

6 に答える 6

65

永久にブロックします。例えば、

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)
    }
}
于 2012-03-03T09:48:20.607 に答える
33

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()必要があることです。そのため、少なくとも 1 つのゴルーチンを作成する必要がありますがmain()、後続のゴルーチンはプログラムの任意の部分で作成できます。

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()
}
于 2012-03-03T08:13:39.560 に答える
26

Go のランタイムパッケージには、必要な機能runtime.Goexitを正確に実行するという関数があります。

メイン ゴルーチンから Goexit を呼び出すと、 func main が返されることなく、そのゴルーチンが終了します。func main が戻っていないので、プログラムは他のゴルーチンの実行を続けます。他のすべてのゴルーチンが終了すると、プログラムはクラッシュします。

遊び場での例

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")
}
于 2016-08-09T16:59:15.203 に答える
19

誰も言及しなかったsignal.Notify(c chan<- os.Signal, sig ...os.Signal)

例:

package main

import (
    "fmt"
    "time"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    go forever()

    quitChannel := make(chan os.Signal, 1)
    signal.Notify(quitChannel, syscall.SIGINT, syscall.SIGTERM)
    <-quitChannel
    //time for cleanup before exit
    fmt.Println("Adios!")
}

func forever() {
    for {
        fmt.Printf("%v+\n", time.Now())
        time.Sleep(time.Second)
    }
}
于 2020-12-03T13:42:44.987 に答える
0

スーパーバイザー ( http://supervisord.org/ )を使用してプロセスをデーモン化できます。あなたの関数は永遠にそれが実行するプロセスにすぎず、関数 main の一部を処理します。スーパーバイザー制御インターフェースを使用して、プロセスを開始/シャットダウン/チェックします。

于 2012-03-03T05:54:47.057 に答える