• select文を使用することで並行操作の優先順の問題を解決することができる。各チャネルを「公平に」扱わないとstarvationが起こってしまう

12.4.1 selectとcase

  • select文を使うことで複数のチャネルに対する操作の記述が可能になる
    • select文の各caseはチャネルに対する読み込み、あるいは書き込み
    • select文はすべてのcaseを評価し、即座に実行可能(ready)な通信があれば、その中から1つをランダムに選んで実行する
package main
 
import "fmt"
 
func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	ch3 := make(chan int)
	ch4 := make(chan int)
 
	go func() {
		v := 1
		fmt.Printf("この下でch1へ%vを入れる\n", v)
		ch1 <- v
	}()
 
	go func() {
		v := 2
		fmt.Printf("この下でch2へ%vを入れる\n", v)
		ch2 <- v
	}()
 
	go func() {
		fmt.Printf("この下でch3から値を受け取る\n")
		v := <-ch3
		fmt.Printf("ch3から%vを受け取った\n", v)
	}()
 
	go func() {
		v := 4
		fmt.Printf("この下でch4へ%vを入れる\n", v)
		ch4 <- v
		fmt.Printf("ch4へ%vを入れた\n", v)
	}()
 
	x := 3
 
	select {
	case v := <-ch1:
		fmt.Println("ch1から読み込み:", v)
	case v := <-ch2:
		fmt.Println("ch2から読み込み:", v)
	case ch3 <- x:
		fmt.Println("ch3へ書き込み: ", x)
	case <-ch4:
		fmt.Println("ch4から値をもらったが、値は無視した")
	}
}

12.4.2 デッドロックの回避

  • デッドロック(互いに相手を待つことにより前に進めない)の例として下記のような場合がある
    • 一方がチャネルch1から送信しようとし、もう一方がチャネルch2から受信しようとしている
    • どちらも同じチャネルから送信しようとしている、あるいは同じチャネルから受信しようとしている
    • 受信先が見つからない場合
package main
 
import (
	"fmt"
)
 
func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
 
	go func() {
		ch1 <- "ゴルーチンから送信した文字列" // ch1に書き込めない限りここで待たされる
		v1 := <-ch2
		fmt.Println(v1)
	}()
 
	ch2 <- "mainから送信した文字列" // ch2に書き込めない限りここで待たされる
	v2 := <-ch1
	fmt.Println(v2)
}
  • デッドロックになるとGoのランタイムがプログラムを強制終了する
    • fatal error: all goroutines are asleep - deadlock!
  • こうした場合にselect文を使用するとデッドロックを回避できる
package main
 
import "fmt"
 
func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
 
	go func() {
		ch1 <- "ゴルーチンから送信した文字列"
		fromMain := <-ch2
		fmt.Println("無名関数:", fromMain)
	}()
 
	var fromGoroutine string
	select { // チャネルでのやり取りをselectで囲む
	case ch2 <- "mainから送信した文字列": // こちらは最初は書き込めない
	case fromGoroutine = <-ch1: // ch1に書き込まれればこれが実行される
	}
	fmt.Println("main:", fromGoroutine)
}

12.4.3 for-selectループ

  • select文はfor`ループの中に埋め込まれることがよくある
  • switch文と同様に、select文もdefault節を持つことができる。読み込みあるいは書き込みができるチャネルがない場合に選択される
	for {
		select {
		case v := <-ch:
			fmt.Println("chから読み込み:", v)
		default:
			fmt.Println("chから読み込める値はなし")
		}
	}
  • ただし、defaultがあると待ちが発生しないので、それが意図的であるか注意が必要