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 の背景や設計思想は関連書籍に詳しい。

関連用語