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

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

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

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

Q&A

解決済

1回答

2993閲覧

Goの複数のgoroutineに対して、一斉ブロードキャストを行いたい

TomMurphy

総合スコア13

Go

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

0グッド

0クリップ

投稿2021/11/19 05:53

前提・実現したいこと

現在、Golangを使ってGIS系の沢山の人間や自動車が動き回るシミュレータを作成しております。

具体的にはgoroutineの中でstructで定義したオブジェクトを大量に発生させて、それらをバラバラに動かすことを考えております。

時々、そのすべてのgoroutineの中のオブジェクトに対して、main()より指示を出したいと思っています(例えば、『現在の位置情報を私(main())に教えろ』というようなもの)。

そこで、単一のchannelを使ったブロードキャストのようなことを試みたいと思っています。
複数のgoroutineに同時に指示が出せるのであれば、手段は問いません。

何卒ご教示頂けますよう、よろしくお願い申し上げます。

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

これについては、(下記のコードのように)goroutineの数だけchannelを作れば実現可能であることは分かったのですが、問題点が2つほどあります。 (1)作成するgoroutineの数が大きいこと(数千から数万) (2)goroutineは不定期に発生して、(役目を終えたら)自動的に消滅する(させる) となっております。

該当のソースコード

main.go

1package main 2 3import ( 4 "fmt" 5 "sync" 6 "time" 7) 8 9type BUS struct { 10 number int 11} 12 13func bus_func(i int, wg *sync.WaitGroup, ch1 chan int, ch2 chan int) { 14 defer wg.Done() 15 16 t := time.NewTicker(1 * time.Second) // 1秒おきに通知 17 18 bus := BUS{} 19 bus.number = i 20 21 pointer_bus := &bus 22 23 fmt.Println(pointer_bus) 24 25 // 待受 26 for { 27 select { 28 case v := <-ch1: // 受信 29 if v == -1 { //終了コードの受信 30 return // スレッドを自滅させる 31 } else { 32 fmt.Println(v) 33 } 34 case <-t.C: // 送信 (1秒まったら、むりやりこっちにくる) 35 ch2 <- i + 100 36 } 37 } 38 39} 40 41//var c = make([]chan int, 3) // 間違い 42// var c []chan int これは実行時にエラーとなる 43 44var c1 [3]chan int // チャネルをグローバル変数で置く方法の、現時点での正解 45var c2 [3]chan int // チャネルをグローバル変数で置く方法の、現時点での正解 46 47func main() { 48 var wg sync.WaitGroup 49 defer wg.Wait() 50 51 //c := make(chan int) 52 53 // バスエージェントの生成 3台 54 for i := 0; i < 3; i++ { 55 wg.Add(1) 56 57 c1[i] = make(chan int) 58 c2[i] = make(chan int) 59 go bus_func(i, &wg, c1[i], c2[i]) 60 } 61 62 // バスエージェントにメッセージを送る 63 64 c1[0] <- 50 65 c1[1] <- 30 66 c1[2] <- 10 67 68 c1[0] <- 50 69 c1[1] <- 30 70 c1[2] <- 10 71 72 c1[0] <- 50 73 c1[1] <- 30 74 c1[2] <- 10 75 76 fmt.Println(<-c2[0]) 77 fmt.Println(<-c2[1]) 78 fmt.Println(<-c2[2]) 79 80 c1[0] <- -1 // スレッド終了コードの送信 81 c1[1] <- -1 // スレッド終了コードの送信 82 c1[2] <- -1 // スレッド終了コードの送信 83 84} 85

試したこと

上記に記載するサンプルプログラムでは、発生させたgoroutineとの間で個別にchannelを作る必要があり、goroutineの管理(発生から消滅まで)をしなければなりません。

(当初、『これくらいのことは簡単に見つかるだろう』と思っていたのですが、昨日1日かけて発見することができませんでした)

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

go version go1.17 windows/amd64

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

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

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

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

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

guest

回答1

0

ベストアンサー

いっせいブロードキャストをするのに一般的に使われているのはPubSubと呼ばれる方式です。

サブスクライブを複数あらかじめおこなっておき、パブリッシュでメッセージを送ると複数のサブスクライブ先に同じメッセージを配信するというものです。

おそらくこの方式は発見済みで、想像するに複数のサブスクライブ先をループで巡って複数回送信することでブロードキャスト相当を実現するのではなく、もっと真にブロードキャストしたいということが質問者さんの意図なのかなと。

そういうものを実現するのに「sync.Cond」という標準ライブラリ機能があります。
これの活用方法は実はちゃんとした実例が見つけにくいです。たいてい前者のやり方で済ましてしまっているのと、sync.Condの挙動は若干わかりづらいです。

すこし解説は端折りますが、以下のように記述することで実現できると思います。

ポイントは

  • タイミングだけをsync.CondのBroadcastで伝える
  • 複数のタスクには共有メモリを通して渡したいメッセージを伝えます
  • 送る方も受ける方も排他ロックを併用するのがCondの使い方でロック期間であれば共有メモリをコンフリクトなくアクセスできます

解答例

go

1package main 2 3import ( 4 "fmt" 5 "log" 6 "sync" 7 "time" 8) 9 10type BroadCaster struct { 11 cond *sync.Cond 12 id int64 13 msg string 14} 15 16func (bc *BroadCaster) Send(msg string) { 17 bc.cond.L.Lock() 18 defer bc.cond.L.Unlock() 19 bc.id++ 20 bc.msg = msg 21 bc.cond.Broadcast() 22} 23 24func (bc *BroadCaster) Recv(last int64) (int64, string) { 25 bc.cond.L.Lock() 26 defer bc.cond.L.Unlock() 27 for bc.id == last { 28 bc.cond.Wait() 29 } 30 return bc.id, bc.msg 31} 32 33var ( 34 broadcaster = &BroadCaster{ 35 cond: sync.NewCond(&sync.Mutex{}), 36 } 37) 38 39func task(i int) { 40 log.Println("task:", i, " start") 41 defer log.Println("task:", i, " stop") 42 last := int64(0) 43 for { 44 id, msg := broadcaster.Recv(last) 45 last = id 46 log.Println("task:", i, msg) 47 } 48} 49 50func main() { 51 for i := 0; i < 3; i++ { 52 go task(i) 53 } 54 for i := 0; i < 3; i++ { 55 time.Sleep(1 * time.Second) 56 broadcaster.Send(fmt.Sprintf("hello, world: %d", i)) 57 } 58 time.Sleep(1 * time.Second) 59}

補足:

  • この方法はPubSubにくらべ、共有メモリをすべてのgoroutineタスクに伝播したかどうかを保証する仕組みがないです
  • つまり、この方法は「低頻度のイベントを大量のタスクに配信」するか、もしくは「最新の値さえ受け取れればOK」という用途向けです。

投稿2021/11/21 13:13

編集2021/11/21 23:28
nobonobo

総合スコア3367

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

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

TomMurphy

2021/11/25 01:34

お返事が遅れまして申し訳ございません(メールが変なところに飛ばされていました)。 コードまで展開して頂き、本当に嬉しいです。早速試させて頂きます。 引き続き、ご指導頂けましたら幸いでございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問