Golang で複数のgoroutineで、1つのchannelを使い回す場合(要求応答通信)をする際の注意点
Golang で複数のgoroutineで、1つのchannelを使い回す場合(要求応答通信)をする際の注意点について
■Case 1
package main
import (
	"fmt"
	"sync"
)
//var Ch1 chan interface{}
var Ch1 chan interface{}
var mutex sync.Mutex
var wg sync.WaitGroup
func funcA(num int) {
	fmt.Println("Staring funcA No.", num)
	for i := 0; i < 10; i++ {
		mutex.Lock()
		fmt.Println("funcA:", num, " send", i+num*10)
		Ch1 <- i + num*10
		k := <-Ch1
		fmt.Println("				returned:", k)
		mutex.Unlock()
	}
	wg.Done()
}
func echo(num int) {
	fmt.Println("Staring echo No.", num)
	for {
		i := <-Ch1
		Ch1 <- i
	}
}
func main() {
	Ch1 = make(chan interface{}) // チャネルの初期化
	wg.Add(1)
	go funcA(1)
	wg.Add(1)
	go funcA(2)
	wg.Add(1)
	go echo(1)
	wg.Wait()
}結果
funcA: 1  send 15
                                returned: 15
funcA: 1  send 16
                                returned: 16
funcA: 1  send 17
                                returned: 17
funcA: 1  send 18
                                returned: 18
funcA: 1  send 19
                                returned: 19
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x0)
        c:/go/src/runtime/sema.go:56 +0x25
sync.(*WaitGroup).Wait(0x483fc0)
        c:/go/src/sync/waitgroup.go:130 +0x71
main.main()
        C:/Users/ebata/goga/0-14/main.go:52 +0x131
goroutine 8 [chan receive]:
main.echo(0x0)
        C:/Users/ebata/goga/0-14/main.go:32 +0x8a
created by main.main
        C:/Users/ebata/goga/0-14/main.go:50 +0x125
exit status 2■Case 2
# 最後に、close(Ch1)を入れてみた
        wg.Add(1)
	go echo(1)
	wg.Wait()
	close(Ch1)
}
結果
funcA: 2 send 25                                returned: 25
funcA: 2  send 26
                                returned: 26
funcA: 2  send 27
                                returned: 27
funcA: 2  send 28
                                returned: 28
funcA: 2  send 29
                                returned: 29
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x0)
        c:/go/src/runtime/sema.go:56 +0x25
sync.(*WaitGroup).Wait(0x1e3fc0)
        c:/go/src/sync/waitgroup.go:130 +0x71
main.main()
        C:/Users/ebata/goga/0-14/main.go:52 +0x131
goroutine 8 [chan receive]:
main.echo(0x0)
        C:/Users/ebata/goga/0-14/main.go:32 +0x8a
created by main.main
        C:/Users/ebata/goga/0-14/main.go:50 +0x125
exit status 2■Case 3
//wg.Add(1)
	go echo(1)
	wg.Wait()
	close(Ch1)
}結果
funcA: 2  send 29
                                returned: 29
funcA: 1  send 17
                                returned: 17
funcA: 1  send 18
                                returned: 18
funcA: 1  send 19
                                returned: 19
C:\Users\ebata\goga\0-14>結論
(1)channelを応答通信で使いたいのであれば、送信側(funcA)の中身をMutexでロックする必要がある。受信側は、一つだけならロックの必要はない
(2)送信側のgoroutineが消えると、受信側(echo)が『相手がいなくて寂しくなって』エラーを吐くので、chose(Ch1)で、きっちりchannelを殺しておくこと
(3)受信側(echo)が無限待ち"for()"であるなら、wg.Add(1)を発行してはならない。なぜならechoは、原則として終了しないgoroutineだから。
―― しかし、そこまで構わなくてもいいんじゃなかな、と思う。Golangは、相当"過保護"に作られていると思う。
以上