Go言語のgoによるゴルーチン(Goroutine)を使った並行処理をわかりやすく解説

スポンサーリンク
スポンサーリンク

goの概要

並行処理(ゴルーチン) Goの予約語

go

概要goは、新しいゴルーチン(Goroutine)を作成し、関数を並行して実行するためのキーワードです。

  • ゴルーチンは軽量なスレッドのようなもので、大量に作成しても効率的に動作する。
  • goを関数の前に付けることで、その関数が別のゴルーチンで並行実行される。
  • ゴルーチンの終了を待たないため、sync.WaitGroupやチャネルを使って制御する必要がある。

基本的なgoの使い方

goキーワードを使うと、関数を新しいゴルーチンで実行できます。

package main

import (
    "fmt"
    "time"
)

// ゴルーチンとして実行する関数
func hello() {
    fmt.Println("Hello, Goroutine!")
}

func main() {
    go hello() // 新しいゴルーチンを開始
    time.Sleep(time.Second) // メインゴルーチンが終了しないように待機
}

解説:

  • go hello()で、hello関数を新しいゴルーチンとして実行します。
  • ゴルーチンはメインゴルーチンとは独立して動作するため、メインゴルーチンが先に終了するとhello()が実行されない可能性があります。
  • そのため、time.Sleep(time.Second)を入れて、メインゴルーチンが終了する前にhello()が実行されるようにしています。

実行結果:

Hello, Goroutine!

複数のゴルーチンを実行

複数のゴルーチンを作成すると、それぞれが並行して実行されます。

package main

import (
    "fmt"
    "time"
)

func printNumber(num int) {
    fmt.Println("Number:", num)
}

func main() {
    for i := 1; i <= 5; i++ {
        go printNumber(i) // 5つのゴルーチンを作成
    }
    time.Sleep(time.Second) // 全てのゴルーチンの実行を待つ
}

解説:

  • go printNumber(i)を5回実行することで、5つのゴルーチンが並行実行されます。
  • 出力の順番は保証されません(ゴルーチンの実行タイミングによる)。
  • メインゴルーチンが終了すると、未完了のゴルーチンも強制終了されるため、time.Sleep(time.Second)を入れて待機します。

実行結果(順不同):

Number: 3
Number: 1
Number: 5
Number: 2
Number: 4

sync.WaitGroupを使ったゴルーチンの終了待ち

sync.WaitGroupを使うと、すべてのゴルーチンの終了を確実に待つことができます。

package main

import (
    "fmt"
    "sync"
)

func printMessage(msg string, wg *sync.WaitGroup) {
    defer wg.Done() // 処理終了時にカウンタを減らす
    fmt.Println(msg)
}

func main() {
    var wg sync.WaitGroup

    messages := []string{"Go", "is", "awesome!"}

    for _, msg := range messages {
        wg.Add(1) // カウンタを増やす
        go printMessage(msg, &wg)
    }

    wg.Wait() // すべてのゴルーチンの終了を待つ
}

解説:

  • sync.WaitGroupを使用し、ゴルーチンの終了を管理します。
  • ゴルーチンを起動するたびにwg.Add(1)でカウンタを増やし、終了時にwg.Done()で減らします。
  • wg.Wait()を使うことで、すべてのゴルーチンが終了するまでmain関数が終了しないようにします。

実行結果(順不同):

is
Go
awesome!

チャネルを使ったゴルーチン間の通信

チャネルを使うと、ゴルーチン間で安全にデータをやり取りできます。

package main

import (
    "fmt"
)

func sendMessage(ch chan string) {
    ch <- "Hello from Goroutine!"
}

func main() {
    ch := make(chan string)

    go sendMessage(ch)

    msg := <-ch // ゴルーチンからのメッセージを受信
    fmt.Println(msg)
}

解説:

  • ch := make(chan string)でチャネルを作成し、ゴルーチン間の通信を可能にします。
  • sendMessage関数がゴルーチン内でchにデータを送信し、main関数で受信します。

実行結果:

Hello from Goroutine!

注意事項

  • ゴルーチンの終了を待たないとプログラムがすぐ終了する: sync.WaitGroupやチャネルを使って制御する。
  • ゴルーチンの順番は保証されない: 実行順序はランダム。
  • データ競合に注意: 共有データへのアクセスにはsync.Mutexやチャネルを使う。

よくある質問

Q: ゴルーチンはスレッドとは違うのですか?
A: はい。ゴルーチンはOSのスレッドよりも軽量で、Goランタイムによって効率的にスケジューリングされます。
Q: すべての関数をgoで並行実行できますか?
A: 可能ですが、すべてを並行実行すると制御が難しくなるため、適切に設計する必要があります。
Q: ゴルーチンを確実に終了させる方法は?
A: sync.WaitGroupやチャネルを使ってゴルーチンの終了を待つのが一般的です。

まとめ

  • goを使うとゴルーチンを作成し、関数を並行実行できる。
  • メインゴルーチンの終了を待つために、sync.WaitGrouptime.Sleepを使う。
  • チャネルを活用すると、ゴルーチン間の安全な通信が可能。