🖥

Go言語—バッファなしのチャンネルに送受信するとデッドロックが起こる理由

2023/08/26に公開

疑問

表題のとおり。
なぜバッファなしのチャンネルに対して、メイン処理で送受信をおこなうとデッドロックが発生するのか。

バッファリングなし

チャンネルはデフォルトではバッファを持たない。

これが何を意味するかというと、並行処理で走る「レシーバー」の受信準備ができているときにだけ、チャンネルに値を送信できるということだ。

package main

import "fmt"

func main() {
	// バッファを持たないチャンネルを作る
	messages := make(chan string)

	// 別の goroutine で受信準備をする
	go func() { fmt.Println(<-messages) }()

	// メイン処理 ( メインの goroutine ) でチャンネルに値を送信する
	messages <- "value"
}

Playground

バッファリングあり

だがチャンネルにバッファを持たせた場合、レシーバーがいない場合でも、チャンネルに指定個数分の値を持たせることが出来る。

Go by Example: Channel Buffering からほぼそのまま

package main

import "fmt"

func main() {

    // 2個のバッファを持ったチャンネルを作成
    messages := make(chan string, 2)

    // レシーバーは存在しない状態だが、チャンネルは値を二個まで持てる
    messages <- "buffered"
    messages <- "channel"

    // 同じくメイン処理で、チャンネルから二回の受信をおこなえる
    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

Playground

まったく実用的な例ではないが、別の goroutine を走らせなくても、メイン処理だけでチャンネルへの値の送受信が出来ているのが分かる。

(バッファ個数を指定しないと、チャンネルに値を送信しようとした時点でデッドロックが発生し、エラーが起こる)

疑問

ではなぜ、非メインの goroutine の中で送信だけをおこなう場合、デッドロックが発生しないのか。

非メインの goroutine での送信は、他の goroutine の受信を待つ

っぽい。たぶん。

というわけではないことが後ほど分かった。

その後書いた記事 — Go言語—チャンネル、バッファ、ブロック、デッドロックを理解する ( そして楽しいgoroutine ) - Qiita

package main

import "fmt"
import "time"

func main() {
	communicate_channel := make(chan bool)
	orphan_channel := make(chan bool)

	// チャンネルにメッセージを送信しようとする
	// あとでメイン処理で受信がおこなわれる
	go func() {
		fmt.Println("Before send message to communicate channel") // Run
		communicate_channel <- true                               // Run
		fmt.Println("After sent message to communicate channel")  // Run
	}()

	// もうひとつのチャンネルにメッセージを送信しようとする
	// だがどこでも受信はおこなわれないので、実際の送信はおこなわれない (デッドロックも起こらない)
	go func() {
		fmt.Println("Before send message to orphan channel") // Run
		orphan_channel <- true                               // Wait
		fmt.Println("After sent message to orphhan channel") // Not run
	}()

	// 片方のチャンネルだけからメッセージを受信する
	<-communicate_channel

	time.Sleep(300 * time.Millisecond)
}

Playground

感想

  • あくまでチャンネルは、goroutine間の値の送受信のためのものなので、メイン処理だけで使おうとしてもエラーが起るのは当たり前。(という理解)
  • 最初、チャンネルが持つバッファの位置づけというか、役割がまったく分からなかった。
    • なぜメイン処理だけでチャンネルに値を送受信しようとすると、デッドロックが発生するのか。
    • なぜ (go 文を使うなどして) goroutine で同じことをした場合は、同じ問題が発生しないのか。
    • 並行処理でレシーバーを走らせた場合は、バッファがなくても全く問題ないように見えたので、さらに謎が深かった。
  • この部分を理解するのに相当苦労したが、ふりかえれば全部 Go Example に書かれていた。

環境

  • go version go1.10.3 darwin/amd64

参考

チャットメンバー募集

何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ

Twitter

https://twitter.com/YumaInaura

公開日時

2018-07-28

Discussion