プログラミング

Go言語での並行処理

Go言語での並行処理(Concurrency)は、高いパフォーマンスを実現するために非常に重要です。特に、複数の関数やタスクを並行して実行する場合、Goの並行処理の特徴であるゴルーチン(goroutines)とチャネル(channels)を活用することが非常に有効です。この記事では、Go言語で複数の関数を並行して実行する方法を完全かつ包括的に説明します。

1. Go言語における並行処理の基本

Goは、並行処理を扱うために「ゴルーチン(goroutines)」と呼ばれる軽量なスレッドを使用します。ゴルーチンは、Goプログラム内で非同期的に実行される関数やメソッドです。通常、Goではgoキーワードを使って関数をゴルーチンとして実行します。これにより、関数が非同期に実行され、他のコードと並行して処理が進みます。

2. ゴルーチンの基本的な使い方

ゴルーチンを使って複数の関数を並行して実行する基本的な方法は以下の通りです。

go
package main import "fmt" func task1() { fmt.Println("タスク1が実行中") } func task2() { fmt.Println("タスク2が実行中") } func main() { go task1() // ゴルーチンでtask1を実行 go task2() // ゴルーチンでtask2を実行 // ゴルーチンが終了する前にmain関数が終了しないように待機 var input string fmt.Scanln(&input) }

上記のコードでは、task1task2の両方を並行して実行しています。goキーワードを使って、それぞれの関数をゴルーチンとして呼び出しています。fmt.Scanln(&input)は、ゴルーチンが実行される時間を確保するために使用され、main関数が終了するのを防ぎます。

3. チャネルを使用したゴルーチンの同期

Goの並行処理では、ゴルーチン間でデータをやり取りするために「チャネル(channels)」を使用します。チャネルを使用することで、ゴルーチン間で安全にデータを送受信でき、複数のゴルーチンの終了を待つことも可能です。

以下は、チャネルを使って複数のゴルーチンが終了するのを待つ例です。

go
package main import ( "fmt" "time" ) func task1(ch chan string) { time.Sleep(2 * time.Second) ch <- "タスク1が完了しました" } func task2(ch chan string) { time.Sleep(1 * time.Second) ch <- "タスク2が完了しました" } func main() { ch := make(chan string) go task1(ch) go task2(ch) // ゴルーチンの終了を待つ fmt.Println(<-ch) fmt.Println(<-ch) }

このコードでは、task1task2の実行が終了すると、それぞれのゴルーチンがチャネルchを通じてメッセージを送ります。main関数は<-chを使ってそのメッセージを受け取り、ゴルーチンが終了するのを待ちます。

4. WaitGroupを使用したゴルーチンの同期

sync.WaitGroupは、複数のゴルーチンが終了するのを待機するために使います。WaitGroupを使うと、ゴルーチンの実行が完了するまでmain関数をブロックし、複数のゴルーチンが終了するのを効率的に待つことができます。

go
package main import ( "fmt" "sync" "time" ) func task1(wg *sync.WaitGroup) { defer wg.Done() time.Sleep(2 * time.Second) fmt.Println("タスク1が完了しました") } func task2(wg *sync.WaitGroup) { defer wg.Done() time.Sleep(1 * time.Second) fmt.Println("タスク2が完了しました") } func main() { var wg sync.WaitGroup wg.Add(2) // 2つのゴルーチンを待機 go task1(&wg) go task2(&wg) wg.Wait() // ゴルーチンの終了を待つ fmt.Println("すべてのタスクが完了しました") }

このコードでは、sync.WaitGroupを使って、task1task2が終了するまでmain関数が待機します。wg.Add(2)で2つのゴルーチンの終了を待機し、wg.Done()でそれぞれのゴルーチンが終了したことを通知します。最後にwg.Wait()を呼び出して、すべてのゴルーチンの終了を待機します。

5. ゴルーチンとエラーハンドリング

並行処理を行う際には、エラーが発生する可能性もあるため、適切にエラーハンドリングを行うことが重要です。エラーをチャネルで送信し、main関数で受け取る方法が一般的です。

go
package main import ( "fmt" "errors" "time" ) func task1(ch chan<- error) { time.Sleep(2 * time.Second) ch <- errors.New("タスク1でエラーが発生しました") } func task2(ch chan<- error) { time.Sleep(1 * time.Second) ch <- nil // エラーなし } func main() { ch := make(chan error, 2) go task1(ch) go task2(ch) for i := 0; i < 2; i++ { if err := <-ch; err != nil { fmt.Println(err) } else { fmt.Println("タスクは正常に完了しました") } } }

この例では、task1task2のそれぞれがエラーをチャネルを通じてmain関数に送信します。main関数では、そのエラーを受け取り、エラーハンドリングを行います。

6. 並行処理の最適化

Goの並行処理を最適化するには、タスクの数やゴルーチンの数を適切に管理することが重要です。ゴルーチンは軽量ですが、多くのゴルーチンを起動すると、システムリソースを消費し、パフォーマンスに影響を与える可能性があります。そのため、ゴルーチンの数を制限するためにバッファ付きチャネルやワーカープールのデザインパターンを使用することが推奨されます。

go
package main import "fmt" func worker(id int, ch chan int) { for task := range ch { fmt.Printf("ワーカー %d がタスク %d を処理中\n", id, task) } } func main() { ch := make(chan int, 5) // バッファ付きチャネル for i := 1; i <= 3; i++ { go worker(i, ch) } for i := 1; i <= 10; i++ { ch <- i // タスクをチャネルに送信 } close(ch) // チャネルを閉じてワーカーに終了を通知 }

このコードは、3つのワーカーがそれぞれタスクを処理するというシンプルなワーカープールの例です。タスクはバッファ付きチャネルを通じて送信され、ワーカーは並行してタスクを処理します。

結論

Go言語での並行処理は、効率的に複数の関数やタスクを実行するために非常に強力な手法です。ゴルーチンとチャネルをうまく使うことで、高いパフォーマンスとスケーラビリティを実現できます。ゴルーチンの使用においては、適切な同期手法やエラーハンドリング、そしてリソース管理を考慮することが重要です。

Back to top button