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

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

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

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

Q&A

解決済

1回答

298閲覧

for c := range ch{}節内でchに値を送信する場合のclose(ch)の方法について

knr

総合スコア20

Go

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

0グッド

0クリップ

投稿2020/10/07 14:59

編集2020/10/08 02:43

前提・実現したいこと

goでbufferedChannelを用いて繰り返し処理を書いています。
for rangeを使ってchannelから値を読み込み、節内で呼び出している別の関数hoge()で同じchannelに値を送信するようなソースコードになっています。
hoge()が呼ばれるたびに関数内でchannelに送信する回数は減っていき、最後にはchannelへの送信自体がされなくなるような挙動のイメージです。
channelに値が送信されなくなり、chのバッファが空になったときにcloseをしたいのですが、適切なハンドリングができずに困っています。
恐れ入りますが、どのようにclose処理を書けばいいか(もしくは、こういった書き方はそもそも文法としてふさわしくないか)ご教示いただければと思います。
期待している出力は下記です。

stdout

13 20 31 42 50 61 70 80 9done

発生している問題・エラーメッセージ

該当のソースコード

go

1package main 2 3import ( 4 "fmt" 5 "time" 6) 7 8func main() { 9 ch := make(chan int, 10) 10 ch <- 3 11 go func() { 12 time.Sleep(2 * time.Second) // ここを適切な形に修正したい 13 close(ch) 14 }() 15 for c := range ch { 16 fmt.Println(c) 17 go hoge(ch, c) 18 } 19 fmt.Println("done") 20} 21 22func hoge(ch chan int, n int) { 23 for i := 0; i < n; i++ { 24 ch <- i 25 } 26} 27

試したこと

sync.WaitGroupを使って、下記のように修正してみました。
うまくいく場合もあるのですが、次の繰り返しによるwg.Add(1)より先にhoge()内のwg.Done()が評価されてしまうと、wg.Wait()以降に入ってしまい、次のhogeが実行される前に終了する場合もあります。

go

1package main 2 3import ( 4 "fmt" 5 "sync" 6) 7 8func main() { 9 ch := make(chan int, 10) 10 ch <- 3 11 var wg sync.WaitGroup 12 go func() { 13 wg.Wait() 14 close(ch) 15 }() 16 for c := range ch { 17 wg.Add(1) 18 fmt.Println(c) 19 go hoge(&wg, ch, c) 20 } 21 fmt.Println("done") 22} 23 24func hoge(wg *sync.WaitGroup, ch chan int, n int) { 25 defer wg.Done() 26 for i := 0; i < n; i++ { 27 ch <- i 28 } 29} 30

補足情報(FW/ツールのバージョンなど)

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

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

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

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

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

nobonobo

2020/10/08 00:26

「挙動のイメージ」から具体的な挙動が想像するのが難しいので、 期待している出力(質問者の思ってるうまくいった時の出力)を追記して欲しい。
knr

2020/10/08 02:43

ありがとうございます。追記しました
guest

回答1

0

ベストアンサー

後者のコードだけに言及してみます。
wg.Waitがcloseを待ち構えた構造ですが、
wg.Addのあとにwg.Waitにはいる動作順を保証できていません。

以下のようにwg.Waitの手前にスリープを入れると確実に動作します。
これが良いコードかというと良くないです。
終了条件を整理してちゃんとロジカルにcloseする方が良いと思います。

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

go

1package main 2 3import ( 4 "fmt" 5 "sync" 6 "time" 7) 8 9func main() { 10 ch := make(chan int, 10) 11 ch <- 3 12 var wg sync.WaitGroup 13 go func() { 14 time.Sleep(time.Second) 15 wg.Wait() 16 close(ch) 17 }() 18 for c := range ch { 19 wg.Add(1) 20 fmt.Println(c) 21 go hoge(&wg, ch, c) 22 } 23 fmt.Println("done") 24} 25 26func hoge(wg *sync.WaitGroup, ch chan int, n int) { 27 defer wg.Done() 28 for i := 0; i < n; i++ { 29 ch <- i 30 } 31} 32

作ろうとされている挙動の目的が質問文からは読み取れません。
なので全体のより良い仕組みを提案はできないので細かい点だけアドバイスします。

  • chanを再帰的に利用するのはパズルの発生を招きます。chanの書き込みを行う責任と読み出しを行う責任はしっかり分離しましょう。(現状のコードは読み出し結果から書き込み内容が変化するという実装)
  • chanとsyncパッケージの同期オブジェクトを併用しだすとパズルになってしまいます。そこの整理ができない場合は無理してchanやgoroutineを使わない方が良い結果になることが多いです。
  • ほとんどのケースでWaitGroupを関数引数に渡して使う必要はなくて以下のコードのようにgoroutine起動側に書くことをおすすめします。

go

1go func() { 2 defer wg.Done() 3 hoge(ch, c) 4}()

投稿2020/10/08 03:13

nobonobo

総合スコア3367

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

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

knr

2020/10/08 11:58

chanの使い方に関するアドバイスもいただき、ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問