ChannelはGoにおける中心的な型で、パイプのようなものと考えることができます。これを通じて並行処理のコアユニットはデータを送受信して通信(communication)を行えます。

その演算子は矢印です。<- 。

ch <- v    // 发送值v到Channel ch中
v := <-ch  // 从Channel ch中接收数据,并将数据赋值给v

(矢印の向きがデータの流れる方向です。)

map や slice などのデータ型と同様に、channelは先に作成してから使用する必要があります:

ch := make(chan int)

Channel型

Channel型の定義形式は次のとおりです:

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

それには3種類の定義が含まれます。オプションの<-channelの方向を表します。方向が指定されていない場合、Channelは双方向であり、データの受信も送信もできます。

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)

使用makeChannelを初期化し、容量を設定できます:

make(chan int, 100)

容量(capacity)はChannelが保持できる最大の要素数を表し、Channelのバッファのサイズを表します。
容量を設定しない場合、または容量を0に設定した場合、Channelにはバッファがなく、senderとreceiverの両方が準備できて初めてそれらの通信(communication)が発生します(Blocking)。バッファを設定した場合、ブロックが発生しないこともあります。bufferが満杯になったときだけsendがブロックされ、バッファが空になったときだけreceiveがブロックされます。nil channelは通信しません。

組み込みのcloseメソッドでChannelをクローズできます。

複数のgoroutineが1つのchannelからデータを受信したり、1つのchannelへデータを送信したりできます。追加の同期手段を考慮する必要はありません。

Channelは先入れ先出し(FIFO)のキューとして使用でき、受信したデータと送信したデータの順序は一致します。

channelのreceiveは複数の戻り値をサポートしていますmulti-valued assignment、例えば

v, ok := <-ch

これを使用して、Channelがすでにクローズされているかどうかを確認できます。

  1. send文
    send文はChannelにデータを送信するために使用します。例:ch <- 3。
    その定義は次のとおりです:
SendStmt = Channel "<-" Expression .
Channel  = Expression .

通信(communication)が始まる前に、channelとexpressionは必ず先に評価(evaluated)される必要があります。たとえば、以下の(3+4)は先に7が計算されてからchannelに送信されます。

c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)

sendが実行されるまで(proceed)、通信(communication)はブロックされ続けます。前述のとおり、バッファのないchannelはreceiverが準備できて初めてsendが実行されます。バッファがあり、バッファが満杯でなければ、sendは実行されます。

すでにcloseされたchannelにデータを送信し続けると、run-time panic。

nil channelにデータを送信すると、ずっとブロックされ続けます。

  1. receive演算子
    <-chchannel chからデータを受信するために使用します。この式は、受信できるデータがあるまでブロックされ続けます。

nil channelからデータを受信すると、ブロックされ続けます。

closeされたchannelからデータを受信してもブロックされず、すぐに返ります。送信済みのデータを受信し終えると、要素型のゼロ値(zero value)が返ります。

前述のとおり、追加の戻り値パラメータを使用して、channelがクローズされているかどうかを確認できます。

x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch

OKがfalseの場合、受信したxはゼロ値であり、このchannelがクローズされているか、空であることを示します。

blocking

デフォルトでは、送信と受信は相手方が準備できるまでブロックされ続けます。この方法を使用すると、明示的なロックや条件変数を使わずにgoroutine間で同期を取ることができます。

公式の例のようにx, y := <-c, <-cこの文は、計算結果がchannelに送信されるまで待機し続けます。

import "fmt"
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to 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 // receive from c
    fmt.Println(x, y, x+y)
}

Buffered Channels

makeの2番目のパラメータはバッファのサイズを指定します:ch := make(chan int, 100)。

バッファを使用することで、ブロックをできるだけ回避し、アプリケーションのパフォーマンスを向上させることができます。


Range

for …… rangerange文はChannelを処理できます。

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に送信された値であり、channelがクローズされるまで反復し続けます。上の例で、もしclose(c)コメントアウトすると、プログラムはfor …… rangeその行でブロックされ続けます。


select

selectselect文は、可能なsend操作とreceive操作のセットから選択して処理します。これはswitchswitchに似ていますが、通信(communication)操作のみを処理するために使用されます。
そのcasecaseはsend文でもreceive文でも、あるいはdefault。

receive文は1つまたは2つの変数に値を代入できます。それはreceive操作でなければなりません。

最大で1つのdefault case、それはcaseリストのどこにでも置くことができますが、たいてい最後に置きます。

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を処理できる場合、たとえば複数のchannelがデータを受信できる場合、Goは擬似ランダム(pseudo-random)に1つのcaseを選択して処理します。処理すべきcaseがない場合は、defaultが選択され処理されます。もしdefault caseが存在する場合。もしdefault caseがなければ、selectselect文は、あるcaseが処理を必要とするまでブロックされます。

注意すべき点は、nil channelに対する操作は常にブロックされるということです。default caseがない場合、nil channelのみのselectはブロックされ続けます。

selectselect文はswitchswitch文と同様に、ループではありません。1つのcaseだけを選択して処理します。channelを常に処理したい場合は、外側に無限のforループを追加できます:

for {
    select {
    case c <- x:
        x, y = y, x+y
    case <-quit:
        fmt.Println("quit")
        return
    }
}

timeout

select非常に重要な応用の1つがタイムアウト処理です。前述のとおり、処理すべきcaseがない場合、select文はブロックされ続けます。このとき、タイムアウト操作が必要になるかもしれません。タイムアウトの状況を処理するためです。
次の例では、2秒後にchannel c1にデータを送信します。しかし、selectタイムアウトを1秒に設定しているため、次のように出力されます。timeout 1ではなく、result 1。

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")
    }
}

実際にはこれはtime.Afterメソッドを利用しています。これは型が<-chan Timeの単方向channelを返し、指定された時間に現在時刻を返されたchannelに送信します。


TimerとTicker

時間に関する2つのChannelを見てみましょう。
timerはタイマーであり、将来の単一のイベントを表します。timerに待ち時間を指定できます。timerはChannelを提供し、将来のその時刻にChannelが時間値を提供します。次の例の2行目は約2秒間ブロックされ、時間になるまで実行が続行されません。

timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")

もちろん、単に待つだけであれば、time.Sleepを使用して実現できます。

また、timer.Stopを使用してタイマーを停止できます。

timer2 := time.NewTimer(time.Second)
go func() {
    <-timer2.C
    fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
    fmt.Println("Timer 2 stopped")
}

tickertickerは一定間隔でトリガーされるタイマーで、間隔(interval)ごとにChannelへイベント(現在時刻)を送信します。Channelの受信者は、固定の時間間隔でChannelからイベントを読み取ることができます。次の例では、tickerは500ミリ秒ごとにトリガーされ、出力される時間を確認できます。

ticker := time.NewTicker(time.Millisecond * 500)
go func() {
    for t := range ticker.C {
        fmt.Println("Tick at", t)
    }
}()

timerと同様に、tickerもStopメソッドで停止できます。停止すると、受信者はそれ以上channelからデータを受信しなくなります。

close

組み込みのcloseメソッドを使用してchannelをクローズできます。

channelがクローズされた後のsenderとreceiverの操作をまとめます。
channel cがすでにcloseされている場合、それにデータを送信し続けると、panic: send on closed channel:

import "time"
func main() {
    go func() {
        time.Sleep(time.Hour)
    }()
    c := make(chan int, 10)
    c <- 1
    c <- 2
    close(c)
    c <- 3
}

しかし、このクローズされたchannelからは、送信済みのデータを読み取れるだけでなく、ゼロ値を読み取り続けることもできます。

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を使用して読み取ると、channelがクローズされた後、forループは抜け出します。

c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
    fmt.Println(i)
}

によってi, ok := <-cChannelの状態を確認でき、値がゼロ値か正常に読み取られた値かを判断できます。

c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false

同期

channelはgoroutine間の同期に使用できます。
次の例では、main goroutineはdone channelを介してworkerがタスクを完了するのを待ちます。workerはタスクを完了した後、channelにデータを1つ送信するだけで、main goroutineにタスク完了を通知できます。

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
}

出典:http://colobu.com/2016/04/14/Golang-Channels/