質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Q&A

2回答

3284閲覧

Goroutineでのdeadlockについて

退会済みユーザー

退会済みユーザー

総合スコア0

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

0グッド

1クリップ

投稿2018/01/22 14:02

編集2018/01/23 00:41

以下のコードではすべてが出力されたあとにdeadlockのエラーが出ます。

package main import ( "sync" "github.com/k0kubun/pp" ) func main() { wg := sync.WaitGroup{} sig := make(chan bool) for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { pp.Println(i) if i == 3 { sig <- true } else { sig <- false } wg.Done() }(i) } switch { case result <-sig: if result == true{ pp.Print("True") } else { pp.Print("False") } default: } wg.Wait() close(sig) pp.Print("All Done") }

出力は以下の通りです。

9 1 5 0 2 3 "False"8 6 4 7 fatal error: all goroutines are asleep - deadlock!

しかし、以下のケースではうまくいきます。

package main import ( "sync" "github.com/k0kubun/pp" ) func main() { wg := sync.WaitGroup{} sig := make(chan bool) for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { pp.Println(i) wg.Done() }(i) } wg.Wait() pp.Print("All Done") }
  • どのような違いがあって
  • なぜそうなるのか

をご指導していただきたいです。
よろしくお願いたします

追加

以下のコードではすべてが出力されたあとにdeadlockのエラーが出ます。

package main import ( "sync" "github.com/k0kubun/pp" ) func main() { wg := sync.WaitGroup{} sig := make(chan bool) for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { pp.Println(i) if i == 3 { sig <- true } else { sig <- false } wg.Done() }(i) }      for { switch { case <-sig: pp.Print("True") default: pp.Print("False") } } wg.Wait() close(sig) pp.Print("All Done") }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nobonobo

2018/01/22 23:40

i==3の時にsigにfalseとtrueの二回書き込むのは意図通りなのですか?
nobonobo

2018/01/23 00:36

あとsigから読んだ値がtrueかfalseかにかかわらずTrueを表示しているのも意図通りですか?違いますか?
退会済みユーザー

退会済みユーザー

2018/01/23 00:38

すみません、一つ目の指摘についてはelseを忘れていました。二つ目は今から修正しますが、なぜかシンタックスエラーができるので、困っています。
guest

回答2

0

前者のコードはfunc mainとforループで起動される10個のgoroutine、つまり11個のgoroutineが走ります。

キューに長さを持たないchanを「unbuffered chan」と呼びますが、以下の特徴があります。

  • 書き込みを行うgoroutineと読み出しを行うgoroutineが必要。
  • 読み待ちと書き待ちが同時に待ち状態になった時に通信が成立する。

つまりデッドロックになる事例は
for分最初に起動したgoroutineが書き込み待ちになった状態で
mainのgoroutineがselect文に到達した時は
通信が成立してfalse値を受け取ることができますが、
以後はsigチャンネルを読もうとするgoroutineがいません。
なのでその他のgoroutineは全て書き込み待ちのままになります。

それで唯一待ちになっていなかった
mainのgoroutineがwg.Wait()により待ちにはいると、
全てのgoroutineが待ちに入ったことになり、
これをgoのランタイムが検出するとこの件のような
「all goroutines are asleep - deadlock!」panicを出します。
(なぜわざわざこういうことをするのかはよく考えてみましょう。)

後者のコードはmain以外に待ちに入るコードがないので起動した10個のgoroutineは順当に完走してwg.Wait()コールはちゃんと帰ってきます。

前者のコードを完走するように修正するには、
11個書いたら11個読みだす処理があればOKです。
(ちなみに前者のコードはi==3のときだけsigに2回書いてる)

参考

go

1package main 2 3import ( 4 "fmt" 5 "sync" 6) 7 8func main() { 9 wg := sync.WaitGroup{} 10 sig := make(chan bool) 11 for i := 0; i < 10; i++ { 12 wg.Add(1) 13 go func(i int) { 14 fmt.Println(i) 15 if i == 3 { 16 sig <- true 17 } 18 sig <- false 19 wg.Done() 20 }(i) 21 } 22 23 for i := 0; i < 11; i++ { 24 switch { 25 case <-sig: 26 fmt.Println("True") 27 default: 28 fmt.Println("False") 29 } 30 } 31 32 wg.Wait() 33 close(sig) 34 fmt.Println("All Done") 35}

質問の修正後の追記:

変更は以下の2箇所

  • 「起動したgoroutine群が完走したらsigをクローズする処理」をgoroutineで動かしておく。
  • 無限ループから抜ける処理をいれる。

https://play.golang.org/p/SBuJsGJ417m

go

1package main 2 3import ( 4 "fmt" 5 "sync" 6) 7 8func main() { 9 wg := sync.WaitGroup{} 10 sig := make(chan bool) 11 for i := 0; i < 10; i++ { 12 wg.Add(1) 13 go func(i int) { 14 fmt.Println(i) 15 if i == 3 { 16 sig <- true 17 } else { 18 sig <- false 19 } 20 wg.Done() 21 }(i) 22 } 23 24 go func() { 25 wg.Wait() 26 close(sig) 27 }() 28 29 for { 30 v, ok := <-sig 31 if !ok { 32 break 33 } 34 if v { 35 fmt.Println("True") 36 } else { 37 fmt.Println("False") 38 } 39 } 40 41 fmt.Println("All Done") 42}

投稿2018/01/22 22:08

編集2018/01/23 03:52
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/01/22 22:31 編集

詳細にありがとうございます。 たとえばfor文を無限ループにしたい場合、 ``` for { switch { case <- sig: // ... default:    // ... } } ``` で、どのようにしたらうまく`break`を10個目の`sig`に挟めるでしょうか?
nobonobo

2018/01/23 00:39

無限ループにする場合、終了を伝えたら抜ける処理が必要です。 最も一般的な方法は終わりたいタイミングでclose(sig)して、 無限ループ内でsigが閉じたかどうかの判定を含めるのが良いでしょう。
nobonobo

2018/01/23 00:41 編集

「v,ok := <-sig」とすると受け取った値はvに、!okであれば閉じられたことがわかります。
退会済みユーザー

退会済みユーザー

2018/01/23 00:42

追加に書きました。 無限for文の中身は ``` case v, ok := <- sig: if !ok { break} // .... default: // ... ``` といったような感じでしょうか?
nobonobo

2018/01/23 03:44

はい。ただ、defaultはない方がいいですね(出力待ちではない状態でselectにくるとスルーしてしまいます)
guest

0

参考情報

go

1for { 2 v, ok := <-sig 3 if !ok { 4 break 5 } 6 ... 7}

上記は以下と等価です。

go

1for v := range sig { 2 ... 3}

投稿2018/01/23 03:55

nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問