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は、相当"過保護"に作られていると思う。
以上