goroutine で関数を並行に実行する

Go の最大の特徴は、軽量な並行処理の仕組みである goroutine です。関数の前に go キーワードをつけるだけで、その関数を別の goroutine として並行に実行できます。

goroutine の基本

通常の関数呼び出しは、その関数が終わるまで次の行に進みません。しかし go をつけると、関数の完了を待たずにすぐ次の処理へ進みます。

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    fmt.Println("Hello!")
}

func main() {
    go sayHello()
    fmt.Println("main continues...")
    time.Sleep(100 * time.Millisecond)
}

このコードを実行すると、main continues...Hello! が出力されます。順番は実行タイミングによって変わることがあります。

time.Sleep を入れているのは、main 関数が終了すると全ての goroutine も強制終了してしまうからです。実際のプログラムでは Sleep ではなく、WaitGroup や channel を使って goroutine の終了を待ちます。

匿名関数を goroutine で実行する

名前のない関数(匿名関数)をその場で goroutine として実行することもできます。

package main

import (
    "fmt"
    "time"
)

func main() {
    go func() {
        fmt.Println("匿名関数が実行されました")
    }()

    go func(msg string) {
        fmt.Println(msg)
    }("引数も渡せます")

    time.Sleep(100 * time.Millisecond)
}

匿名関数に引数を渡す場合は、関数定義の直後に () で値を渡します。

goroutine はとても軽い

OS のスレッドは 1 つあたり数 MB のメモリを消費しますが、goroutine は数 KB 程度です。そのため、数千〜数万の goroutine を同時に動かすことも珍しくありません。

OS スレッド

メモリ消費が大きい(数 MB)。生成コストも高く、大量に作るのは難しい。

goroutine

メモリ消費が小さい(数 KB)。Go のランタイムがスケジューリングするため、大量に作っても効率的に動作する。

この軽量さが、Go で並行処理を気軽に書ける理由になっています。

注意点

goroutine を使うときに気をつけることがあります。

まず、main 関数が終わると全ての goroutine も終了します。goroutine の完了を待つ仕組みが必要です(WaitGroup や channel を使います)。

また、複数の goroutine から同じ変数を読み書きすると、データ競合が起きる可能性があります。これを防ぐには Mutex や channel を使います。

これらの対処法は別の記事で詳しく説明します。