Golang Channelのすべて：絶対に知っておくべきこと

[![Group46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/c4e63d9a-2eba-c8d5-0566-453115955009.png)](https://leapcell.io/?lc_t=q_gochannel)

[Leapcell：最適なGolangホスティング用サーバレスプラットフォーム](https://leapcell.io/?lc_t=q_gochannel)

ChannelはGo言語のコア型です。コンカレントコアユニットがデータを送信または受信して通信を実現するためのパイプラインと見なすことができます。その演算子は矢印 `<-` です。

## Channel操作の例
- `ch <- v`：値 `v` をChannel `ch` に送信します。
- `v := <-ch`：Channel `ch` からデータを受信し、そのデータを `v` に割り当てます。（矢印の向きはデータの流れ方向を示します。）

## Channelの作成と使用
`map` や `slice` などのデータ型と同様に、Channelは使用前に作成する必要があります：
```go
ch := make(chan int)
```

## Channelの型
Channel型の定義形式は以下の通りです：
```go
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType.
```
これには3種類の定義が含まれ、省略可能な `<-` はチャネルの方向を表します。方向が指定されていない場合、チャネルは双方向で、データの受信と送信の両方が可能です。
- `chan T`：型 `T` のデータを受信および送信できます。
- `chan<- float64`：型 `float64` のデータを送信するためのみ使用できます。
- `<-chan int`：型 `int` のデータを受信するためのみ使用できます。

`<-` は常に最も左の型と最初に結合します。例えば：
- `chan<- chan int`：`chan<- (chan int)` と同等です。
- `chan<- <-chan int`：`chan<- (<-chan int)` と同等です。
- `<-chan <-chan int`：`<-chan (<-chan int)` と同等です。
- `chan (<-chan int)`。

## makeを使ったChannelの初期化と容量の設定
```go
make(chan int, 100)
```
容量はChannelが保持できる最大要素数、つまりChannelのバッファサイズを表します。容量が設定されていない、または0に設定されている場合、これはChannelにバッファがないことを意味し、送信者と受信者の両方が準備できたときにのみ通信が行われます（ブロッキング）。バッファを設定した後は、ブロッキングが発生しない場合があります。バッファがいっぱいになったときにのみ `send` 操作がブロックされ、バッファが空のときには `receive` 操作がブロックされます。`nil` チャネルは通信しません。

## Channelのクローズ
Channelは組み込みの `close` メソッドを通じてクローズできます。複数のgoroutineは追加の同期対策を考えることなく、チャネルからデータを `receive` したり、チャネルにデータを `send` したりすることができます。Channelは先入先出（FIFO）キューとして機能し、データの受信と送信の順序は一貫しています。

## Channelの受信は複数値の代入をサポート
```go
v, ok := <-ch
```
この方法を使用して、Channelがクローズされたかどうかをチェックできます。

### send文
send文はChannelにデータを送信するために使用され、例えば `ch <- 3` のようになります。その定義は以下の通りです：
```go
SendStmt = Channel "<-" Expression.
Channel  = Expression.
```
通信が開始される前に、チャネルと式の両方が評価される必要があります。例えば：
```go
c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)
```
上記のコードでは、まず `(3 + 4)` が7に計算され、その後チャネルに送信されます。送信が実行されるまで通信はブロックされます。前述のように、バッファのないチャネルの場合、送信操作は受信者が準備できたときにのみ実行されます。バッファがあり、バッファがいっぱいでない場合、送信操作は実行されます。既にクローズされたチャネルにデータを送信し続けると、実行時パニックが発生します。`nil` チャネルにデータを送信すると、無期限にブロックされます。

### receive演算子
`<-ch` はチャネル `ch` からデータを受信するために使用されます。この式はデータを受信できるまでブロックされます。`nil` チャネルからデータを受信すると、無期限にブロックされます。既にクローズされたチャネルからデータを受信すると、ブロックされずに直ちに返ります。送信されたデータを受信した後、要素型のゼロ値を返します。前述のように、追加の戻り値を使用して、チャネルがクローズされたかどうかをチェックできます：
```go
x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch
```
`OK` が `false` の場合、受信した `x` は生成されたゼロ値であり、チャネルがクローズされているか空であることを示します。

### ブロッキング
デフォルトでは、送信と受信は相手が準備できるまでブロックされます。この方法を使用すると、明示的なロックや条件変数を使用せずに、goroutine内で同期を行うことができます。例えば、公式の例：
```go
import "fmt"
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // sumをcに送信
}
func main() {
    s := []int{7, 2, 8, -9, 4, 0}
    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // cから受信
    fmt.Println(x, y, x+y)
}
```
上記のコードでは、`x, y := <-c, <-c` という文は計算結果がチャネルに送信されるのを待ち続けます。

### バッファ付きチャネル
`make` の2番目のパラメータはバッファのサイズを指定します：
```go
ch := make(chan int, 100)
```
バッファを使用することで、できるだけブロッキングを回避し、アプリケーションのパフォーマンスを向上させることができます。

### Range
`for …… range` 文はチャネルを処理できます：
```go
func main() {
    go func() {
        time.Sleep(1 * time.Hour)
    }()
    c := make(chan int)
    go func() {
        for i := 0; i < 10; i = i + 1 {
            c <- i
        }
        close(c)
    }()
    for i := range c {
        fmt.Println(i)
    }
    fmt.Println("Finished")
}
```
`range c` で生成される反復値は、Channelに送信された値です。チャネルがクローズされるまで繰り返します。上記の例で、`close(c)` をコメントアウトすると、プログラムは `for …… range` の行でブロックされます。

### select
`select` 文は、一連の可能な送信と受信操作を選択して処理するために使用されます。`switch` に似ていますが、通信操作の処理にのみ使用されます。その `case` は送信文、受信文、または `default` のいずれかです。受信文は1つまたは2つの変数に値を割り当てることができ、必ず受信操作でなければなりません。最大で1つの `default` ケースが許され、通常はケースリストの最後に配置されます。例えば：
```go
import "fmt"
func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}
```
複数の `case` が同時に処理可能な場合、例えば複数のチャネルが同時にデータを受信できる場合、Goは疑似的にランダムに1つの `case` を選択して処理します（疑似的にランダム）。処理する `case` がない場合、`default` が選択されて処理されます（前提は `default case` が存在すること）。`default case` が存在しない場合、`select` 文は処理する `case` があるまでブロックされます。`nil` チャネルに対する操作は無期限にブロックされることに注意してください。`default case` がない場合、`nil` チャネルのみを含む `select` は無期限にブロックされます。`select` 文は `switch` 文と同様に、ループではなく、1つの `case` のみを選択して処理します。チャネルを継続的に処理するには、外部に無限ループ `for` を追加できます：
```go
for {
    select {
    case c <- x:
        x, y = y, x+y
    case <-quit:
        fmt.Println("quit")
        return
    }
}
```

### タイムアウト
`select` の1つの重要な応用はタイムアウト処理です。処理する `case` がない場合、`select` 文はブロックされるため、このときタイムアウト操作が必要な場合があります。例えば：
```go
import "time"
import "fmt"
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}
```
上記の例では、2秒後にチャネル `c1` にデータが送信されますが、`select` は1秒後にタイムアウトするように設定されています。したがって、`result 1` ではなく `timeout 1` が出力されます。ここでは `time.After` メソッドを使用しています。このメソッドは型 `<-chan Time` の片方向チャネルを返します。指定された時間に、現在の時刻が返されるチャネルに送信されます。

### TimerとTicker
1. **Timer**：これは1つの将来のイベントを表すタイマーです。待機時間を指定でき、チャネルを提供します。指定された時間に、チャネルに時刻値が提供されます。例えば：
```go
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")
```
上記の2行目は約2秒間ブロックされ、時間が到来した後に続けて実行されます。もちろん、単に待機するだけなら、`time.Sleep` を使用して達成できます。また、`timer.Stop` を使用してタイマーを停止することもできます：
```go
timer2 := time.NewTimer(time.Second)
go func() {
    <-timer2.C
    fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
    fmt.Println("Timer 2 stopped")
}
```
2. **Ticker**：これは定期的にトリガーされるタイマーです。一定期間（間隔）でイベント（現在の時刻）をチャネルに送信します。チャネルの受信者は、一定の時間間隔でチャネルからイベントを読み取ることができます。例えば：
```go
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
    for t := range ticker.C {
        fmt.Println("Tick at", t)
    }
}()
```
`timer` と同様に、`ticker` も `Stop` メソッドを通じて停止できます。停止すると、受信者はもはやチャネルからデータを受信できなくなります。

### close
組み込みの `close` メソッドを使用してチャネルをクローズできます。チャネルがクローズされた後の送信者と受信者の操作をまとめると以下の通りです：
 - チャネル `c` がクローズされている場合、そこにデータを送信し続けるとパニックが発生します：`send on closed channel`。例えば：
```go
import "time"
func main() {
    go func() {
        time.Sleep(time.Hour)
    }()
    c := make(chan int, 10)
    c <- 1
    c <- 2
    close(c)
    c <- 3
}
```
 - クローズされたチャネルからは送信されたデータを読み取ることができるだけでなく、ゼロ値を読み続けることもできます：
```go
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
fmt.Println(<-c) //1
fmt.Println(<-c) //2
fmt.Println(<-c) //0
fmt.Println(<-c) //0
```
 - `range` を使って読み取る場合、チャネルがクローズされた後、`for` ループは抜け出します：
```go
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
    fmt.Println(i)
}
```
 - `i, ok := <-c` を使うと、チャネルの状態を確認し、値がゼロ値なのか通常に読み取った値なのかを判断できます：
```go
c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false
```

### 同期
チャネルはゴルーチン間の同期に使用できます。例えば、次の例ではメインゴルーチンが `done` チャネルを通じてワーカーがタスクを完了するのを待ちます。ワーカーはタスクを完了した後、チャネルにデータを送信することでメインゴルーチンにタスクの完了を通知することができます：
```go
import (
    "fmt"
    "time"
)
func worker(done chan bool) {
    time.Sleep(time.Second)
    // タスクが完了したことを通知
    done <- true
}
func main() {
    done := make(chan bool, 1)
    go worker(done)
    // タスクが完了するのを待つ
    <-done
}
```

# [Leapcell：最適なGolangホスティング用サーバレスプラットフォーム](https://leapcell.io/?lc_t=q_gochannel)

最後に、Golangのデプロイに最適なプラットフォームをおすすめします：**[Leapcell](https://leapcell.io/?lc_t=q_gochannel)**

[![barndpic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/a9e2de4a-34c4-0a80-dd86-1ad043f7c2ab.png)](https://leapcell.io/?lc_t=q_gochannel)


### 1. 多言語対応
 - JavaScript、Python、Go、またはRustで開発できます。

### 2. 無制限のプロジェクトを無料でデプロイ
 - 使用量に応じてのみ課金 — リクエストがなければ料金はかかりません。

### 3. 圧倒的なコスト効率
 - 使い放題でアイドル料金はかかりません。
 - 例：25ドルで平均応答時間60msで694万回のリクエストをサポートできます。

### 4. 合理化された開発者体験
 - 直感的なUIによる簡単なセットアップ。
 - 完全自動化されたCI/CDパイプラインとGitOps統合。
 - アクション可能なインサイトを得るためのリアルタイムメトリックとロギング。

### 5. 簡単なスケーラビリティと高パフォーマンス
 - 高い並行性を簡単に処理するための自動スケーリング。
 - オペレーションオーバーヘッドゼロ — 構築に集中できます。

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/87596cea-f960-c064-36aa-25112c6da831.png)](https://leapcell.io/?lc_t=q_gochannel)


[ドキュメントで詳細を探索！](https://docs.leapcell.io/)

LeapcellのTwitter：[https://x.com/LeapcellHQ](https://x.com/LeapcellHQ)