Goroutine Management

ゴルーチンは軽量ですが、無料ではありません:少なくとも、スタックとCPUスケジューリングのためにメモリを消費します。これらのコストはゴルーチンの使用に対しては小さいですが、制御されていないライフサイクルで大量に生成すると、深刻なパフォーマンスの問題につながる可能性があります。管理されていないライフサイクルを持つゴルーチンによって、使用されていないオブジェクトのガベージコレクションが防がれ、未使用のリソースが保持されるという問題も発生する可能性があります。

そのため、コードでゴルーチンをリークさせないようにしてください。go.uber.org/goleakを使用して、ゴルーチンリークをテストし、ゴルーチンを生成する可能性のあるパッケージ内でのリークを確認してください。

一般的に、各ゴルーチンは次の条件を満たす必要があります:

  • 予測可能な停止時間を持つこと
  • ゴルーチンに対して停止することを通知する方法を持つこと

どちらの場合も、コードでゴルーチンの完了を待機する方法がある必要があります。

例:

非推奨:

go func() {
  for {
    flush()
    time.Sleep(delay)
  }
}()
// このゴルーチンを停止する方法はありません。アプリケーションが終了するまで無期限に実行されます。

推奨:

var (
  stop = make(chan struct{}) // ゴルーチンに停止を知らせる
  done = make(chan struct{}) // ゴルーチンが終了したことを知らせる
)
go func() {
  defer close(done)
  ticker := time.NewTicker(delay)
  defer ticker.Stop()
  for {
    select {
    case <-ticker.C:
      flush()
    case <-stop:
      return
    }
  }
}()

// ... 他のコード
close(stop)  // ゴルーチンを停止することを示す
<-done       // ゴルーチンの終了を待機する

// このゴルーチンはclose(stop)で待機することができ、<done>で終了を待機することができます。

ゴルーチンの終了を待機

システムによって生成されたゴルーチンがある場合、そのゴルーチンの終了を待機する方法がある必要があります。これを達成するための一般的な方法は2つあります:

  • sync.WaitGroup を使用する。複数のゴルーチンを待機する場合に使用してください。
var wg sync.WaitGroup
for i := 0; i < num; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // ...
    }()
}
wg.Wait()
  • 別の chan struct{} を追加し、ゴルーチンが完了した後にそのチャネルを閉じる。1つのゴルーチンの場合にこの方法を使用してください。
done := make(chan struct{})
go func() {
    defer close(done)
    // ...
}()
// ゴルーチンの完了を待機するには:
<-done

init() 関数はゴルーチンを作成すべきではありません。また、init()の使用を避ける を参照してください。

パッケージがバックグラウンドゴルーチンを必要とする場合、そのゴルーチンのライフサイクルを管理するオブジェクトを公開する必要があります。このオブジェクトは、バックグラウンドゴルーチンの終了を示し、その終了を待機するためのメソッド(CloseStopShutdownなど)を提供する必要があります。

非推奨アプローチ:

func init() {
    go doWork()
}
func doWork() {
    for {
        // ...
    }
}
// ユーザーがこのパッケージをエクスポートすると、バックグラウンドゴルーチンが無条件に生成されます。ユーザーはゴルーチンを制御したり停止したりすることができません。

推奨アプローチ:

type Worker struct{ /* ... */ }
func NewWorker(...) *Worker {
    w := &Worker{
        stop: make(chan struct{}),
        done: make(chan struct{}),
        // ...
    }
    go w.doWork()
}
func (w *Worker) doWork() {
    defer close(w.done)
    for {
        // ...
        select {
        case <-w.stop:
            return
        }
    }
}
// Shutdown はワーカーに停止するように指示し、その完了を待機します。
func (w *Worker) Shutdown() {
    close(w.stop)
    <-w.done
}

ユーザーが要求したときにのみワーカーを生成し、ワーカーが使用するリソースを解放できるようにするために、ワーカーをシャットダウンするメソッドを提供してください。なお、ワーカーが複数のゴルーチンを管理する場合は、WaitGroup を使用する必要があります。