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がすでにクローズされているかどうかを確認できます。
- 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にデータを送信すると、ずっとブロックされ続けます。
- 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/