Goのゴルーチンとチャネルを理解しよう

# はじめに
Goは並行処理を簡単に、そして効率的に実装できるプログラミング言語として知られています。その中心的な概念が「ゴルーチン」と「チャネル」です。本記事は、「ゴルーチン」と「チャネル」について学んだことのアウトプットです。

# ゴルーチン
### 概念
ゴルーチンは、Goプログラムで並行処理を実現するための基本的なスレッドです。従来のOSスレッドと比較して、非常に軽量で、少ないリソースで並行処理を実行することができます。

Goでは、ゴルーチンを使って並行処理を行う際に、通常、関数の呼び出し時にgoを付けてその関数を別スレッド（ゴルーチン）として実行させます。これにより、プログラム内で複数のゴルーチンが並行して実行されるようになります。

:::note info
#### ゴルーチンとOSスレッドの違い

　OSスレッドの場合、OSがスレッドのスケジューリングを管理します。これにはコンテキストスイッチング（スレッドの切り替え）が含まれますが、このプロセスは時間がかかります。
　一方、ゴルーチンはGoランタイムによって管理されており、コンテキストスイッチが高速で、ゴルーチン間の切り替えが容易であるため、並行処理を効率的に行うことができます。
:::



#### 主な特徴
- 非常に軽量（数キロバイトのメモリしか消費しない）
- 簡単に生成できる
- 多数のゴルーチンを同時に実行可能
- OSのスレッドとは異なり、Goランタイムによって管理される


### 実装して理解する
```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
```
:::note info
#### ゴルーチンを使わなかった時の出力
`printNumbers()` の処理が終わってから `printLetters()` を呼び出していることがわかりますね。
```
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Letter: A
Letter: B
Letter: C
Letter: D
Letter: E
Main goroutine finished
```

:::



# チャネル
### 概念
チャネルは、ゴルーチン間で安全にデータをやり取りするための仕組みです。データを送信したり、受信したりするために使用され、ゴルーチン間の通信を可能にします。

#### 主な特徴
- 型付きのパイプのような通信機構
- 送信と受信をブロックする
- デッドロックを防ぐための組み込みのメカニズム
- バッファありとバッファなしの2種類が存在

### 実装して理解する
```go
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つのワーカーゴルーチンが並行に動作

```go
for w := 1; w <= 3; w++ {
    go worker(w, jobs, results)
}
```

`range`を使用してジョブチャネルからデータが渡された時のみ動作する

```go
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
    }
}
```

ジョブは利用可能なワーカーに自動的に分配
```go
// ジョブを送信
for j := 1; j <= 5; j++ {
    jobs <- j
}
```

結果は別のチャネルで収集
```go
results <- job * 2
```

:::note info
#### 補足
- 送信側がデータをチャネルに送るとき、受信側が受け取るまで処理はブロック（停止）されます。
- 受信側も送信側からデータが来るまでブロックされます。
- `close(chan)`を使ってチャネルを明示的に閉じることができます。
:::

# 参考
https://go.dev/doc/effective_go#channels