はじめに
Goは並行処理を簡単に、そして効率的に実装できるプログラミング言語として知られています。その中心的な概念が「ゴルーチン」と「チャネル」です。本記事は、「ゴルーチン」と「チャネル」について学んだことのアウトプットです。
ゴルーチン
概念
ゴルーチンは、Goプログラムで並行処理を実現するための基本的なスレッドです。従来のOSスレッドと比較して、非常に軽量で、少ないリソースで並行処理を実行することができます。
Goでは、ゴルーチンを使って並行処理を行う際に、通常、関数の呼び出し時にgoを付けてその関数を別スレッド(ゴルーチン)として実行させます。これにより、プログラム内で複数のゴルーチンが並行して実行されるようになります。
主な特徴
- 非常に軽量(数キロバイトのメモリしか消費しない)
- 簡単に生成できる
- 多数のゴルーチンを同時に実行可能
- OSのスレッドとは異なり、Goランタイムによって管理される
実装して理解する
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Printf("Number: %d\n", i)
}
}
func printLetters() {
for letter := 'A'; letter <= 'E'; letter++ {
time.Sleep(150 * time.Millisecond)
fmt.Printf("Letter: %c\n", letter)
}
}
func main() {
go printNumbers()
go printLetters()
// メインゴルーチンが終了しないよう、待ち時間を入れる
time.Sleep(1 * time.Second)
fmt.Println("Main goroutine finished")
}
このコードでは、printNumbers()
と printLetters()
の2つの関数を別々のゴルーチンで実行しています。go
キーワードを使うことで、関数が並行して実行されます。
動作確認
go run main.go
以下のようにNumberとLetterの動作が同時に動いていることが見て取れます。
Number: 1
Letter: A
Number: 2
Letter: B
Number: 3
Number: 4
Letter: C
Number: 5
Letter: D
Letter: E
Main goroutine finished
チャネル
概念
チャネルは、ゴルーチン間で安全にデータをやり取りするための仕組みです。データを送信したり、受信したりするために使用され、ゴルーチン間の通信を可能にします。
主な特徴
- 型付きのパイプのような通信機構
- 送信と受信をブロックする
- デッドロックを防ぐための組み込みのメカニズム
- バッファありとバッファなしの2種類が存在
実装して理解する
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
// 3つのワーカーゴルーチンを起動
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// ジョブを送信
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// 結果を収集
for a := 1; a <= 5; a++ {
fmt.Println(<-results)
}
}
コード説明
3つのワーカーゴルーチンが並行に動作
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
range
を使用してジョブチャネルからデータが渡された時のみ動作する
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second)
results <- job * 2
}
}
ジョブは利用可能なワーカーに自動的に分配
// ジョブを送信
for j := 1; j <= 5; j++ {
jobs <- j
}
結果は別のチャネルで収集
results <- job * 2
参考