logo
Updated

goroutine

related: go


goroutineの処理完了の注意点

NG

🚨 危険なパターン(参考)
  // ❌ 危険な例(待機なし)
  
  func dangerousExample() []string {
      urls := []string{"http://api1.com", "http://api2.com", "http://api3.com"}
      results := make([]string, len(urls))

      for i, url := range urls {
          go func(index int, u string) {
              resp := fetchData(u)  // HTTP APIを呼び出し
              results[index] = resp
          }(i, url)
      }

      return results  // ❌ goroutineの完了を待たずにreturn!
  }

問題点:

goroutineが完了する前に関数が終了

結果が空の配列になる可能性

HTTPリクエストが途中で切断される

安全な実装

パターン1: チャネルを使った同期

  func safeChannelPattern() []string {
      urls := []string{"http://api1.com", "http://api2.com", "http://api3.com"}

      // 結果を受け取るチャネル
      resultCh := make(chan struct{
          index int
          data  string
      }, len(urls))

      // 全goroutineを起動
      for i, url := range urls {
          go func(index int, u string) {
              resp := fetchData(u)
              resultCh <- struct{
                  index int
                  data  string
              }{index, resp}
          }(i, url)
      }

      // 全goroutineの完了を待機 ✅
      results := make([]string, len(urls))
      for i := 0; i < len(urls); i++ {
          result := <-resultCh  // ブロッキング操作
          results[result.index] = result.data
      }

      return results
  }

パターン2: WaitGroupを使った同期

  func safeWaitGroupPattern() []string {
      urls := []string{"http://api1.com", "http://api2.com", "http://api3.com"}

      var wg sync.WaitGroup
      var mu sync.Mutex
      results := make([]string, len(urls))

      for i, url := range urls {
          wg.Add(1)  // 待機対象を増やす
          go func(index int, u string) {
              defer wg.Done()  // 完了を通知

              resp := fetchData(u)

              mu.Lock()
              results[index] = resp
              mu.Unlock()
          }(i, url)
      }

      wg.Wait()  // 全goroutineの完了を待機 ✅
      return results
  }

並列数制御

セマフォによる並行数制御

大量のgoroutineを起動する場合、リソース消費を抑えるためにセマフォで同時実行数を制御しま
す。


  

  func controlledConcurrency(userIDs []int, maxConcurrency int) map[int]string {
      // セマフォ: 同時実行数を制限
      semaphore := make(chan struct{}, maxConcurrency)

      resultCh := make(chan struct{
          userID int
          data   string
      }, len(userIDs))

      for _, userID := range userIDs {
          go func(id int) {
              // セマフォを取得(空きがなければ待機)
              semaphore <- struct{}{}
              defer func() { <-semaphore }()  // セマフォを解放

              data := fetchUserData(id)  // 時間のかかる処理
              resultCh <- struct{
                  userID int
                  data   string
              }{id, data}
          }(userID)
      }

      // 全結果を収集
      results := make(map[int]string)
      for i := 0; i < len(userIDs); i++ {
          result := <-resultCh
          results[result.userID] = result.data
      }

      return results
  }

Contextを使ったタイムアウト対応

  func withTimeoutPattern(ctx context.Context, urls []string) ([]string, error) {
      resultCh := make(chan struct{
          index int
          data  string
          err   error
      }, len(urls))

      for i, url := range urls {
          go func(index int, u string) {
              resp, err := fetchDataWithContext(ctx, u)
              resultCh <- struct{
                  index int
                  data  string
                  err   error
              }{index, resp, err}
          }(i, url)
      }

      results := make([]string, len(urls))
      for i := 0; i < len(urls); i++ {
          select {
          case result := <-resultCh:
              if result.err != nil {
                  return nil, result.err
              }
              results[result.index] = result.data

          case <-ctx.Done():
              // タイムアウトまたはキャンセル
              return nil, ctx.Err()
          }
      }

      return results, nil
  }
  | パターン      | 安全性  | 使いやすさ | パフォーマンス | 用途             |
  |-----------|------|-------|---------|----------------|
  | チャネル      | ✅ 高  | 🔸 中  | 🔸 中    | 結果の順序が重要でない場合  |
  | WaitGroup | ✅ 高  | ✅ 高   | ✅ 高     | シンプルな同期が必要な場合  |
  | セマフォ      | ✅ 高  | 🔸 中  | ✅ 高     | リソース制限が必要な場合   |
  | Context   | ✅ 最高 | 🔸 中  | 🔸 中    | タイムアウト対応が必要な場合 |

🎯 用途別の推奨パターン

API呼び出しの並列化: Context + セマフォ

データ処理の並列化: WaitGroup

ファイル処理: セマフォ

リアルタイム処理: チャネル

⚠️ よくある間違い

  1. goroutineの完了を待たない
  // ❌ ダメな例
  go func() { /* 処理 */ }()
  return result  // すぐにreturn
  2. 競合状態(Race Condition)
  // ❌ ダメな例
  results := make([]string, len(urls))
  for i, url := range urls {
      go func(index int, u string) {
          results[index] = fetchData(u)  // 同期なし
      }(i, url)
  }
  3. セマフォのサイズが不適切
  // ❌ ダメな例
  semaphore := make(chan struct{}, len(urls))  // 制限なし

参考文献