type: #tech Technology
related: go
🚨 危険なパターン(参考)
// ❌ 危険な例(待機なし)
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
}
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)) // 制限なし