goroutine
Go の軽量スレッドで、数百万の並行処理を低コストで実現する
Go並行処理
goroutine とは
goroutine は Go の軽量スレッドで、go キーワードで関数を並行実行する。OS スレッド (数 MB のスタック) と異なり、goroutine は数 KB のスタックで起動し、数百万の goroutine を同時に実行できる。Go ランタイムが goroutine を OS スレッドにマッピングする (M:N スケジューリング)。
OS スレッドとの比較
| 観点 | OS スレッド | goroutine |
|---|---|---|
| スタックサイズ | 1〜8 MB (固定) | 数 KB (動的に拡張) |
| 生成コスト | 高い | 低い |
| 同時実行数 | 数千 | 数百万 |
| スケジューリング | OS カーネル | Go ランタイム |
| コンテキストスイッチ | 遅い (カーネル空間) | 速い (ユーザー空間) |
基本的な使い方
func main() {
go processOrder("order-1") // goroutine で並行実行
go processOrder("order-2")
go processOrder("order-3")
time.Sleep(time.Second) // goroutine の完了を待つ (簡易版)
}
WaitGroup で完了を待つ
func main() {
var wg sync.WaitGroup
orders := []string{"order-1", "order-2", "order-3"}
for _, id := range orders {
wg.Add(1)
go func(id string) {
defer wg.Done()
processOrder(id)
}(id)
}
wg.Wait() // 全 goroutine の完了を待つ
}
channel との組み合わせ
func fetchUser(id string, ch chan<- User) {
user := db.GetUser(id) // DB から取得
ch <- user // channel に送信
}
func main() {
ch := make(chan User, 3)
go fetchUser("1", ch)
go fetchUser("2", ch)
go fetchUser("3", ch)
for i := 0; i < 3; i++ {
user := <-ch // channel から受信
fmt.Println(user.Name)
}
}
Node.js の async/await との比較
| 観点 | goroutine | async/await (Node.js) |
|---|---|---|
| モデル | 軽量スレッド (プリエンプティブ) | イベントループ (協調的) |
| CPU バウンド | ✅ 複数コアを活用 | ❌ シングルスレッド |
| I/O バウンド | ✅ | ✅ |
| メモリ共有 | あり (Mutex で保護) | なし (シングルスレッド) |
Lambda (Go ランタイム) での goroutine
func handler(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
ch := make(chan string, 2)
go func() { ch <- fetchFromDB(event.PathParameters["id"]) }()
go func() { ch <- fetchFromCache(event.PathParameters["id"]) }()
result := <-ch // 先に返った方を使う
return events.APIGatewayProxyResponse{StatusCode: 200, Body: result}, nil
}
よくある間違い
- goroutine のリーク: channel の受信側がいないと goroutine が永遠にブロック
- データ競合: 共有変数への同時アクセス →
sync.Mutexや channel で保護
goroutine の背景や設計思想は関連書籍に詳しい。