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

以上

 

 

2022/04,江端さんの技術メモ

Posted by ebata