概要
golang のチャネルは goroutine を使って非同期にメッセージの送受信を行うことができます
自分はキューみたいなイメージしています
このサイトが非常にわかりやすいのでこれをベースに進めます
環境
- macOS 10.14.1
- golang 1.11.2
準備
mkdir $GOPATH/github.com/hawksnowlog/chn_test
touch $GOPATH/github.com/hawksnowlog/chn_test/main.go
とりあえずサンプルを動かす
vim $GOPATH/github.com/hawksnowlog/chn_test/main.go
package main
import "fmt"
func main() {
messages := make(chan string)
go func() { messages <- "ping" }()
ret := <-messages
fmt.Println(ret)
}
go build github.com/hawksnowlog/chn_test
go install github.com/hawksnowlog/chn_test
$GOPATH/bin/chn_test
で「ping」と表示されます
messages
というチャネルを作成します
messages
は文字列の送受信を行うことができるチャネルです
そしてそのチャネルに対してアロー演算子を使ってメッセージの送受信を行うことができます
<- "ping"
とすることでメッセージの送信を行います
<-messages
とすることでメッセージの受信を行います
上記のサンプルでは ping を送信して送信した ping を受信して表示しています
チャンネルバッファリング
言うなれば複数のメッセージをチャネルに対して送受信できるようにする機能です
package main
import "fmt"
func main() {
messages := make(chan string, 2)
messages <- "ping"
messages <- "ping2"
ret1 := <-messages
ret2 := <-messages
fmt.Println(ret1)
fmt.Println(ret2)
}
2 つのメッセージを送信することができます
受信する順番は先に入れたメッセージになります
なので「ping」->「ping2」の順番で表示されます
ちなみにメッセージを受信する前に 3 つ目のメッセージを送信すると fatal error: all goroutines are asleep - deadlock!
というエラーが発生します
すでに 2 つメッセージが入っている場合は一度メッセージを受信しなければなりません
チャネル同期
メインの処理と goroutine の処理は基本非同期です
が、チャネルを使うことで goroutine 側の処理を待ってメインの処理を進めることができます
package main
import "fmt"
import "time"
func worker(messages chan bool) {
fmt.Println("start")
time.Sleep(5 * time.Second)
fmt.Println("end")
messages <- true
}
func main() {
messages := make(chan bool, 1)
go worker(messages)
<-messages
}
5 秒待つ関数を goroutine で呼び出しています
その引数にチャネルを渡します
チャネルは bool 型のメッセージを 1 つ持つことができるようにします
ちなみに最後の <-messages
をコメントアウトするだけで worker 側の処理を待たずしてメイン側は終了してしまいます
チャネルディレクション
チャネル間でメッセージの送受信をやり取りする際に専用の関数を準備することで型の保証を担保することができます
package main
import "fmt"
func ping(pings chan string, msg string) {
pings <- msg
}
func pong(pings chan string, pongs chan string) {
msg := <-pings
pongs <- msg
}
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "ping")
pong(pings, pongs)
fmt.Println(<-pongs)
}
select 構文との組み合わせ
チャネルが複数ある場合に使えます
また複数のチャネルに対して goroutine を使って処理させる場合、それぞれの goroutine での処理が終了してからそれぞれの次の実行を実行することができます
package main
import "fmt"
import "time"
func main() {
m1 := make(chan string, 1)
m5 := make(chan string, 1)
go func() {
time.Sleep(1 * time.Second)
m1 <- "m1"
}()
go func() {
time.Sleep(5 * time.Second)
m5 <- "m5"
}()
select {
case msg := <-m1:
fmt.Println(msg)
case msg := <-m5:
fmt.Println(msg)
}
}
1 秒後の m1 が表示され 5 秒後の m5 が表示されるのがわかると思います
それぞれの goroutine の処理が終了するのを待つことが分かると思います
また m1, m5 にメッセージがない状態で case 分の評価がされると fatal error: all goroutines are asleep - deadlock!
になるので注意してください
タイムアウト処理
goroutine と組み合わせる場合 goroutine 側の処理が異常に長くなり返ってこないことも考えられます
その場合は time.After
+ select 文を使ってタイムアウト処理を実現することができます
package main
import "fmt"
import "time"
func main() {
m1 := make(chan string, 1)
go func() {
time.Sleep(3 * time.Second)
m1 <- "m1"
}()
select {
case msg := <-m1:
fmt.Println(msg)
case <-time.After(2 * time.Second):
fmt.Println("m1 goroutine timeout")
}
m2 := make(chan string, 1)
go func() {
time.Sleep(1 * time.Second)
m2 <- "m2"
}()
select {
case msg := <-m2:
fmt.Println(msg)
case <-time.After(2 * time.Second):
fmt.Println("m2 goroutine timeout")
}
}
<-time.After
という感じでアロー演算子を付与することを忘れないようにしてください
それぞれ goroutine の処理は 2 秒しか待ちません
m1 はメッセージを送信するのに 3 秒かかり m2 は 1 秒で完了します
なので結果としては m1 側はタイムアウトとなり m2 側はメッセージの受信まで完了します
ノンブロッキングなチャネル
普通のチャネルはブロッキングです
前にも紹介しましたが普通に使っていれば deadlock が発生する可能性があります
それを回避する方法もあり select の default 文を使うことで回避できます
package main
import "fmt"
func main() {
ss := make(chan string, 1)
bb := make(chan bool, 1)
select {
case msg := <-ss:
fmt.Println(msg)
default:
fmt.Println("message not available")
}
bb <- true
select {
case msg := <-bb:
fmt.Println(msg)
default:
fmt.Println("message not available")
}
}
メッセージが存在しない場合には default 文側に遷移します
最後に
golang のチャネルを実際にコーディングし挙動を確認しながら学んでみました
非同期処理の実現や select 文と合わせたテクニックなど駆使することでかなりいろいろなケースに対応できるテクニックかなと思いました
紹介したサイトのチャネルのユースケースはまだまだ続くので興味があれば試してみてください
続きはここからです
0 件のコメント:
コメントを投稿