2012-03-03 19 views
29

私はGoで書かれた長い実行サーバーを持っています。メインは、プログラムのロジックが実行されるいくつかのゴルーチンを起動します。その後、メインは役に立たない。メインが終了すると、プログラムは終了します。私が今プログラムを実行し続けるために使用している方法はfmt.Scanln()の単純な呼び出しです。私は他の人がどのようにメインから退出するのか知りたいです。以下は基本的な例です。ここではどのようなアイデアやベストプラクティスを使用できますか?長時間実行しているGoプログラムを実行するにはどうすればよいですか?

私はチャンネルを作成し、そのチャンネルで受信してメインの出口を遅らせることを検討しましたが、私のゴルーチンがある時点ですべて非アクティブになると問題になるかもしれません。

サイドノート:私のサーバ(例ではありません)では、プログラムは実際にシェルに接続されているわけではないので、とにかくコンソールとやりとりするのは本当に意味がありません。現時点では機能していますが、私は「正しい」方法を探しています。

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) 
    } 
} 
+0

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.

例私はそれを作るための6つの方法見つかった - のhttp:// pliutauを。 – pltvs

答えて

18

Goのランタイムの現在の設計では、プログラマがいつゴルーチンを終了するのか、いつプログラムを終了するのかを検出することを前提としています。プログラマは、ゴルーチンとプログラム全体の終了条件を計算する必要があります。プログラムは、通常の方法で、os.Exitを呼び出すか、またはmain()関数から戻ることによって終了させることができます。

が終了しないようにするには、チャンネルを作成してmain()の終了を遅らせることが有効です。しかし、プログラムを終了させるタイミングを検出する問題は解決しません。

main()機能が待ちオールゴルーチンツー終了ループに入る前にゴルーチンの数が計算できない場合は、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()を呼び出す前に呼び出されるということなので、その後のゴルーチンは、プログラムのどの部分で作成することができながら、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() 
} 
+0

徹底的な答えと大きな説明。私は、チャンネルの受信に行くだろう、私の行くルーチンは、サービングを行う他のパッケージに埋もれているので。私はゴルーチンをどこでも数えたいとは思わない。信号や他のIPCを使ってプログラムにシャットダウンロジックを追加することに決めたら、チャンネルはうまくいくでしょう...ありがとう! – Nate

1

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

+0

興味深いシステムですが、実際のプログラムだと思いますが、私が使用するGoコードではありません。私は主に退出を防ぐためのベストプラクティスを明らかにしようとしているので、私は自分の質問を編集するつもりです。今、私はfmt.Scanln()を使用していますが、もっと良い方法があるかどうかを知りたいです...しかし、スーパーバイザーに感謝します。私はそれをさらに調査することができました。 – Nate

36

永遠にブロック。例えば、ここで

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) 
    } 
} 
+0

シンプル。完了したジョブを取得します。 Upvote。 – Nate

+0

このアプローチは、最近のgoでうまく動作していないようです。goroutine 1 [select(no cases)]: main.main() ' –

+1

@dark_ruby:これはGoの最新バージョンで動作します:' go version devel + 13c44d2 Sat Jun 20 10:35:38 2015 +0000 linux/amd64'。どのように我々はあなたの失敗を正確に再現できますか?非常に正確です。たとえば、「最新のもの」はあまりにも曖昧です。 – peterSO

1

は永遠チャンネル

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) 
    } 
} 
8

を使用して、簡単なブロックであるゴーのruntimeパッケージには、正確に何をしたいんだろうruntime.Goexitと呼ばれる機能があります。 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") 
} 
+0

これは非常にクールです。しかし、私はなぜ彼らが優雅に退出するのではなく、プログラムをクラッシュさせることを選択したのか疑問に思う必要があります。アプリケーションのシャットダウンを処理する別のゴルーチンがあり、シグナルを受け取ったときに終了を呼び出すという考えですか?私はちょうど誰がこの機能を適切に使うのだろうと思っています。 – Nate

+1

@Nateは正しいです。例えば、この[code](https://play.golang.org/p/8N5Yr3ZNPT)を見て、Goルーチン2はプログラムを終了し、プログラムはクラッシュしません。 Goexitがあなたのプログラムをクラッシュさせた場合、他のすべてのGoルーチンが完了しているからです。私はそれがクラッシュするのが大好きです。何も起こっていなくても永遠にブロックする 'select {}'とは異なり、何もしていないことを私に教えてください。 – jmaloney

+0

これはより多くのupvotesを必要とします。 –