この記事の一部は機械翻訳を使ってるよ
Golang シリーズ
Hello GoLang: https://blog.yexca.net/ja/archives/154
GoLang (var and const) 変数と定数: https://blog.yexca.net/ja/archives/155
GoLang (func) 関数: https://blog.yexca.net/ja/archives/156
GoLang (slice and map) スライス: https://blog.yexca.net/ja/archives/160
GoLang (OOP) オブジェクト指向: https://blog.yexca.net/ja/archives/162
GoLang (reflect) リフレクション: https://blog.yexca.net/ja/archives/204
GoLang (struct tag) 構造タグ: https://blog.yexca.net/ja/archives/205
GoLang (goroutine) ゴルーチン: https://blog.yexca.net/ja/archives/206
GoLang (channel) チャンネル: この記事
ゴルーチン (Goroutine) は channel を通じてデータを転送できます。参照型 channel は複数の Goroutine が通信するために使用できます。同時実行の安全性を確保するために、内部で同期を実装します。
これは、 RabbitMQ (簡体字中国語) に似ています (これは学習に便宜上の例えです、実は異なるものです)
変数を定義する
channel は参照型です。関数をコピーまたは呼び出すと、同じ channel オブジェクトが参照されます。ゼロ値は nil です。
make() 関数によって作成される。例えば
c := make(chan int)
// 容量を 3 に追加
c := make(chan int, 3)
容量が 0 の場合、channel はバッファリングされず、読み取りと書き込みがブロックされます。 容量が 0 より大きい場合、キャッシュがあり、非ブロッキングであり、いっぱいになるまでブロックされません。
データの送受信には <-
を使用します
// channel にデータを送信する
channel <- 3
// 受信して破棄
<-channel // スペースがないことに注意してください
// 値を受け取って変数に割り当てる
x := <-channel
// 受信して変数に割り当て、受信が成功したかどうか(channel が空かどうか)を判定します
data, flag := <-channel
バッファリングなし
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
defer fmt.Println("A.defer")
c <- 6
fmt.Println("A running")
}()
num := <-c
fmt.Println("num =", num)
fmt.Println("main over")
}
バッファリング
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3)
go func() {
defer fmt.Println("A over")
for i := 0; i < 3; i++ {
c <- i
fmt.Println("A goroutine, i =", i, "len =", len(c), "cap =", cap(c))
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ {
num := <-c
fmt.Println("main, num =", num)
}
fmt.Println("main over")
}
channel を閉じる
close()
で閉じる
package main
import "fmt"
func main() {
c := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
// 閉じる
close(c)
}()
for {
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("Main Finished")
}
range の使用
上記の main の for ループは、range を使用して短縮することができます。
package main
import "fmt"
func main() {
c := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
}()
//for {
// if data, ok := <-c; ok {
// fmt.Println(data)
// } else {
// break
// }
//}
for data := range c {
fmt.Println(data)
}
fmt.Println("Main Finished")
}
一方向 channel
デフォルトでは、channel は双方向、つまり読み取りと書き込みが可能です。チャネルの方向を読み取り専用または書き込み専用に指定することもできます。
var c chan int // 通常の双方向チャネルを宣言する
// c1 は書き込みのみ可能
var c1 chan<- int
// c2 は読み取り専用です
var c2 <-chan int
双方向チャネルを一方向チャネルに変換することは可能ですが、その逆はできません。つまり、関数パラメータを一方向として定義し、それを双方向に渡すことができます。
// chan<- // 書き込みのみ
func counter(out chan<- int) {
defer close(out)
for i := 0; i < 5; i++ {
out <- i // 相手が読んでいない場合はブロックします
}
}
// <-chan // 読み取り専用
func printer(in <-chan int) {
for num := range in {
fmt.Println(num)
}
}
func main() {
c := make(chan int) // 双方向
go counter(c) // プロデューサー
printer(c) // 消費者
fmt.Println("done")
}
select
select は複数の channel 上のデータ フローを監視できます。構文は switch に似ていますが、各 case ステートメントには IO 操作が含まれている必要があります。
通常は for{}
コードブロック内に置く
package main
import "fmt"
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
// main
x, y := 1, 1
for {
select {
case c <- x:
tmp := x
x = y
y = tmp + y
case <-quit:
fmt.Println("quit")
return
// デフォルトを設定することもできますが、この例では不要です
}
}
}