「The Go Memory Model」の読解


「The Go Memory Model」
https://golang.org/ref/mem
の理解をまとめる。

Channelの3つのルール

上の記事に、Goのchannnelの送受信に関する3つのルールが載っている。

ルール1

A send on a channel happens before the corresponding receive from that channel completes.
(チャネルへの送信は、それに対応する受信が完了する前に行われる。)

ルール2

A receive from an unbuffered channel happens before the send on that channel completes.
(バッファなしのチャネルに対しては、送信が完了する前に、受信が行われる。)

ルール3

The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes.
(バッファ容量Cのチャネルからのk+C番目の送信が完了する前に、k番目の受信が行われる。)


そして、上記の補足としてこう書いてある。
This rule generalizes the previous rule to buffered channels.
(ルール3はルール1と2の一般化である)


具体例で理解する

前提

package main
var a string

func f() {
    a = "hello"
}

func main() {
    go f()
    print(a) // 空文字が出力
}

printによる出力は、関数fの完了を待たない。

ルール1の例

package main
var c = make(chan int, 10)
var a string

func f() {
    a = "hello"
    c <- 1 // 値はなんでも良い
}

func main() {
    go f()
    <-c
    print(a) // helloと出力
}

バッファ有りなので、<-c(受信)の前にc <- 1(送信)が行われる決まりである。送信の前にaにhelloが代入されているため、helloと出力される。

ルール2の例

package main
var c = make(chan int)
var a string

func f() {
    a = "hello"
    <-c
}

func main() {
    go f()
    c <- 1 // 値はなんでも良い
    print(a) // helloと出力
}

バッファ無しなので、入力c <- 1の前に出力<-cが行われる。

日本語が変な感じもするが、要は複数のgoroutineに処理がまたがった時、どういう順番でプログラムが実行されるかということを言っている。

上の例でmainとfは異なるgoroutineなので、本来互いの動作は干渉し合わない。が、cというチャネルを利用することで、2つの動作を連携させ、処理の順番を制御することができる。

より具体的には、バッファ無しの際の動作は以下のようになる。

mainの方の処理がc <- 1に差し掛かった時、c <- 1の処理を完了する前に、fの<-cという処理が行われる。もしfがそれより前の処理を行っているのであれば、fの<-cが完了するまで、mainは待機する。

「ルール3はルール1と2の一般化である」の意味

ルール1,2は、ルール3にまとめることができる。

まず、バッファ無しをバッファ0と読み替えると、ルール3の文章は次のようになる。

バッファ無しのチャネルからのk番目の送信が完了する前に、k番目の受信が生じる。

これは、ルール2と同じことを言っている。送信c <- hogeが完了する前に、<-cが完了する。

バッファ有りに関しては、kとCにいくつか値を代入して確認してみる。

バッファ1のチャネルからの2番目の送信が完了する前に、1番目の受信が生じる。

バッファ2のチャネルからの3番目の送信が完了する前に、1番目の受信が生じる。

バッファ100のチャネルからの101番目の送信が完了する前に、1番目の受信が生じる。

チャネルのバッファ数を超える数の送信c <- hogeを行う場合は、その前にバッファを減らす操作、つまり受信<-cを行わなければならないということを言っている。
つまり、バッファを超える場合は、バッファ無しのチャネルの場合と同じだということ。

また、

バッファ1のチャネルからの1番目の送信が完了する前に、0番目の受信が生じる(受信は生じない)。

バッファ100のチャネルからの50番目の送信が完了する前に、-50番目の受信が生じる(≒受信は生じない)。

つまり、バッファを超えないような送信は、受信を待つ必要はない、ということを言っている。
そして、これはルール1が言いたかったことである。

これらを踏まえると、ルール3が1と2のまとめであるということが理解できる。

例えで理解する

以下のstack overflowで、上記の内容の質問&回答が行われている。
回答者の例えが分かりやすかった。
https://stackoverflow.com/questions/46822673/how-to-understand-the-ch

まとめると次のようなものである。

チャネル・・・玄関
チャネルに出し入れする値・・・宅配物
goroutine1(送信側)・・・宅配業者
goroutine2(受信側)・・・家に住んでいる人

バッファ有りのチャネルは、宅配ボックス付きの玄関と考えることができる。
宅配の際に相手が家にいる必要はないので、宅配業者は荷物を宅配ボックスに放り込んで、次の仕事を行うことができる。(処理の継続ができる)

バッファ無しのチャネルは、宅配ボックスが無い玄関と考えることができる。
宅配の際には相手が家にいなければならない。家にいない場合は、相手が帰ってくるまで玄関の前で待っていなくてはならない1。(処理をストップしなければならない)



以上になります。内容に誤りがありましたら、是非コメントでお知らせいただけますと幸いです。


  1. 現実世界では不在票を投函して次の仕事に行く。