Go by Example を読んでいたが、チャンネルやバッファ周りの理解にかなり苦労した。
疑問
- そもそもチャンネルって何?
- バッファリングって何のためにあるの?
- デッドロックってどんな状態?
- なぜメイン処理ではチャンネルにバッファが必要になるのか?
- なぜgoroutineの中ではバッファなしのチャンネルが動くのか?
ちなみにこれは、私の理解
間違っているところなどあればお知らせいただきたい。
チャンネルとは何か?
Go言語のチャンネルは複数のgoroutineがお互いにコミュニケーションためのもの。
チャンネルを通して、goroutine同士はメッセージ(値)をやり取りする。
- ここでは便宜的に「値の受け渡し」を「メッセージの受け渡し」と呼ぶ。
チャンネルは橋というか、ハブというか、パイプというか、そんな感じのもの。
goroutine を理解する
チャンネルを理解する前にgoroutineの理解が必要だ。
メイン処理は他のgoroutineを生むことが出来るが、実はメイン処理自体もgoroutineで回っている。
package main
import "fmt"
import "runtime"
func main() {
// Goroutine num includes main processing
fmt.Println(runtime.NumGoroutine()) // 1
// Spawn two goroutines
go func() {}()
go func() {}()
// Total three goroutines run
fmt.Println(runtime.NumGoroutine()) // 3
}
チャンネルは全てのgoroutineを等しく扱うはず。それがメイン処理のgoroutineだろうと、派生したgoroutineだろうと。
送受信
goroutineはチャンネルにメッセージを送ることが出来る。
チャンネルはメッセージを受け取ると、それを「今すぐ」に他のgoroutineに送ろうとする。
package main
import "fmt"
func main() {
messages := make(chan string)
// Send message
go func() { messages <- "Hello" }()
// Receive message
fmt.Println(<-messages) // Hello
}
ブロック
例:
- 1つのgoroutineがチャンネルからメッセージを受け取ろうとする
- しかしチャンネルの中身が空の時
- なおかつ、他のgoroutineが走っている時
このケースでは、受信側(メッセージを受け取ろうとしているgoroutine)は、チャンネルからメッセージが取り出せるのを待つことになる。
デッドロックが起こらないのは、他のgoroutineがチャンネルにメッセージを送信するかもしれないから。なので諦めずに「待つ」。
ブロックが起こる例 : 眠れる送信者
package main
import "fmt"
import "time"
func main() {
messages := make(chan string)
// In spawned goroutine
//
// Send message to channel
// But before do it sleep for a while
go func() {
time.Sleep(1000 * time.Millisecond)
messages <- "Hello"
}()
// In main goroutine
//
// Receive message from channel
// Message appears after spawned goroutine awaked from sleeping
// Channel is empty until other goroutine send message
// So this receiving will be blocked for a while
fmt.Println(<-messages) // Hello
}
ブロックが起こる例 : 眠れる受信者
package main
import "fmt"
import "time"
func main() {
messages := make(chan string)
// Receiver
go func() {
fmt.Println("Receiver : I am waiting for your message.")
msg := <-messages
fmt.Println("Receiver : I got a mail.")
fmt.Println(msg)
}()
// Sender
time.Sleep(2000 * time.Millisecond)
messages <- "Message : Do you like go language?"
// Wait spawned goroutine process
time.Sleep(1000 * time.Millisecond)
}
デッドロック
デッドロックはgoroutineが1個だけしか走っていない時に起こる。
つまり、他のgoroutineは存在しないか、走り終えてしまっている時に。
「二個以上のgroutineが走っているが、両方ロック状態」の場合などにもデッドロックは起こる
理論的に送受信のどちらもおこなえなくなった場合に起こると思われる
あとで調査・修正する
例:
- goroutine がチャンネルからメッセージを受信しようとする
- しかしチャンネルは空
- 他のgoroutineも走っていない
この場合、メッセージを受信できる見込みはないので、受信を諦めてデッドロックエラーが起こる。
デッドロックの例 : 送信者の不在
package main
func main() {
messages := make(chan string)
// Do nothing spawned goroutine
go func() {}()
// A groutine ( main groutine ) trying to send message to channel
// But no other groutine runnning
// And channel has no buffers
// So it raises deadlock error
messages <- "I wanna tell you." // fatal error: all goroutines are asleep - deadlock!
}
デッドロックの例 : 受信者の不在
package main
func main() {
messages := make(chan string)
// Do nothing spawned goroutine
go func() {}()
// A groutine ( main groutine ) trying to receive message from channel
// But channel has no messages, it is empty.
// And no other groutine running. ( means no "Sender" exists )
// So channel will be deadlocking
<-messages // fatal error: all goroutines are asleep - deadlock!
}
さらに疑問
Q. 何故派生したgoroutineではデッドロックが起こらないのか
こういう理由ではないだろうか。
なぜならメインがgoroutineは常に走っているから。
派生したgoroutineから見ると「他のgoroutineが一人もいない」という事態は起こりえないのだ。
そして、メインのgoroutineが走り終えたときは、そもそもプロセスが走り終わる時、全てのgoroutineが走り終わる時。
なので実質的に、派生したgoroutineでデッドロックは起こらない。
(もしかしたら起こすことも出来るかも知れないが、Go言語に詳しい人に聞きたい)
環境
- go version go1.10.3 darwin/amd64
Gist
チャットメンバー募集
何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。