Goの複数のgoroutineに対して、一斉ブロードキャストを行いたい
https://teratail.com/questions/370090?nli=619ad631-d4fc-405f-a649-44670a040506#reply-502318
もの凄く助かりました。
nobonoboさんに心からの感謝を
C:\Users\ebata\goga\3-10
/*
いっせいブロードキャストをするのに一般的に使われているのはPubSubと呼ばれる方式です。
サブスクライブを複数あらかじめおこなっておき、パブリッシュでメッセージを送ると
複数のサブスクライブ先に同じメッセージを配信するというものです。
おそらくこの方式は発見済みで、想像するに複数のサブスクライブ先をループで巡って
複数回送信することでブロードキャスト相当を実現するのではなく、もっと真に
ブロードキャストしたいということが質問者さんの意図なのかなと。
そういうものを実現するのに「sync.Cond」という標準ライブラリ機能があります。
これの活用方法は実はちゃんとした実例が見つけにくいです。たいてい前者のやり方で済まして
しまっているのと、sync.Condの挙動は若干わかりづらいです。
すこし解説は端折りますが、以下のように記述することで実現できると思います。
ポイントは
タイミングだけをsync.CondのBroadcastで伝える
複数のタスクには共有メモリを通して渡したいメッセージを伝えます
送る方も受ける方も排他ロックを併用するのがCondの使い方でロック期間であれば
共有メモリをコンフリクトなくアクセスできます
この方法はPubSubにくらべ、共有メモリをすべてのgoroutineタスクに伝播したか
どうかを保証する仕組みがないです
つまり、この方法は「低頻度のイベントを大量のタスクに配信」するか、もしくは
「最新の値さえ受け取れればOK」という用途向けです。
*/
package main
import (
"fmt"
"log"
"sync"
"time"
)
type BroadCaster struct {
cond *sync.Cond
id int64
msg string
}
func (bc *BroadCaster) Send(msg string) {
bc.cond.L.Lock()
defer bc.cond.L.Unlock()
bc.id++
bc.msg = msg
bc.cond.Broadcast()
}
func (bc *BroadCaster) Recv(last int64) (int64, string) {
bc.cond.L.Lock()
defer bc.cond.L.Unlock()
for bc.id == last {
bc.cond.Wait()
}
return bc.id, bc.msg
}
var (
broadcaster = &BroadCaster{
cond: sync.NewCond(&sync.Mutex{}),
}
)
func task(i int) {
log.Println("task:", i, " start")
defer log.Println("task:", i, " stop")
last := int64(0)
for {
id, msg := broadcaster.Recv(last)
last = id
log.Println("task:", i, msg)
}
}
func main() {
for i := 0; i < 3; i++ {
go task(i)
}
for i := 0; i < 3; i++ {
time.Sleep(1 * time.Second)
broadcaster.Send(fmt.Sprintf("hello, world: %d", i))
}
time.Sleep(1 * time.Second)
}
Keyword sync.Cond, sync.Broadcast,
PrumeMobileクライアントの作り方
C:\Users\ebata\goga\3-23に格納されている(絶対忘れると思ったので、アップしておく)
対向サーバは、〜/go_template/tests/server23.go
小文字で始まるField名は外部パッケージからのアクセスできない
【Golang】structのField名で気をつけるところ
「小文字で始まるField名は外部パッケージからのアクセスできない」 って、どんな言語仕様なんだ・・・
いや、privateとかpublicなんぞ使うよりずっといいって思うよ。
でも、こんなことでvscodeから怒られているとは思わなかったよ。
Golangのファイルを分割して使いたい
ソースコードが長くなってくれば、ソースコードを分割するのは当然ですが、Golangでは、この「当たり前」の情報が全然見つからなくて、本当に困っています。
これまで出てきたエラー一覧
- build command-line-arguments: cannot find module for path _/C_/Users/....
- # command-line-arguments
- repeated module statement
「もう、これだから、新しい言葉を覚えるのは嫌なんだよ」と、何十回目かの泣き言を言っています。C/C++で1000万くらいのスレッドが、サクっと作れれば、別にGolangでなくたって・・・と思うのですが、ないものをねだっても仕方がありません。
こちらの記事(https://leben.mobi/go/configuration_and_package/start-go/)を丸パクリさせて頂きました(ちょっとだけ変えています)。
$ tree
.
├── hello
│ ├── greet.go
│ └── hello.go
└── main.go
てな構造でディレクトリを掘って、ファイルを作ってください。
で、
$ go mod init m ← "m"でもなんでも、好きな文字列を(ここでは"m"とします)
go: creating new go.mod: module m
go: to add module requirements and sums:
go mod tidy
下↓が必要はどうかは不明
$ go mod edit -replace=m/hello=../hello
として、
.
├── go.mod
を作ります。
$ more main.go
package main
import (
"fmt"
"m/hello" // ← ここ重要
)
func main() {
fmt.Println(hello.SayHello()) // パッケージ名 + 最初が大文字の関数
fmt.Println(hello.Greet())
}
$ more hello/hello.go
package hello // ← ここ重要
func SayHello() string { // 最初は大文字
return "Hello World!"
}
$ more hello/greet.go package hello // ← ここ重要 func Greet() string { // 最初は大文字 return "How are you?" }
とすると、
$ go run main.go
Hello World!
How are you?
と、ちゃんと、動くようになりました。
が、全然消せない。
みなさん。 golang の ":="は、変数宣言せずに変数を使える便利な奴ですが、スコープ(有効範囲)を間違えて、二重使用すると「地獄」を見ますよ。
みなさん。 golang の ":="は、変数宣言せずに変数を使える便利な奴ですが、スコープ(有効範囲)を間違えて、二重使用すると「地獄」を見ますよ。
/////a_Point := liPoint
a_Point := liPoint
for {
/////b_Point := a_Point // aがスタート、bがゴール
b_Point := a_Point // 現在のゴールをスタートにする
a := <-ch3 // センタからch3待ち(ゴールを教えてもらう)
if a != -1 {
a_Point = li[a.(int)]
この最後の行を、
a_Point := li[a.(int)]
となっていて、訳の分からない変数の追跡で、数時間失いました。
―― VR(Virtual Reality)のエロが凄い
以前、こちらの日記に、
という内容を掲載しました。
あの時は、会社での飲み会だったので、あまり詳しい話が聞けませんでした。
この度、私のコラムをご愛読して頂いている読者のMさんより(EE Times JapanのMさん(Ms.M)とは別の方)、詳細なレポートを頂きました。
VRを理解する上で、優れたコンテンツと思いましたので、Mさんの許諾を得て、以下に公開させて頂きます。
====== ここから =====
VRの最大の特徴は「人は視覚と聴覚を仮想されると、それを現実として認識する」です。
VRとの出会いはゲームでした。
独身一人暮らし時代、VRでバイオハザードという一人称視点のホラーゲームをプレイしていました。
プレイ中にムービーがあったんですが、その内容は
「自分が椅子に拘束され、両手首も前手で拘束された状態から、チェーンソーで両手首を切断される」
というものでした。
(自分自身もソファに座って、コントローラーを持っていたので、ムービーと全く同じ体制を取っていました)
仮想現実の自分が、両手首を切断されたとき、いの一番に自分は叫びました。
「痛ッッたくない!!!」
-----
VRでをしばらく装着すると、「ここが仮想現実である」という認識がどんどんなくなって、「仮想現実が現実化」します。
その時チェーンソーで切られた手首は間違いなく自分の手首であり、「痛くない」ことで、これがVRだったことを思い出すほどにです。
また、エースコンバットという戦闘機のフライトシミュレーションゲームのVR版をプレイした時は、ヴァーティゴに陥り、VRがなければ戦闘機パイロットでしか経験できないであろう経験をしました。
VRは現実です。
ゲームプレイ後、VRを外して見える自分の部屋を見て
「なんで俺は(洋館やコックピットでなく)ここにいるんだ?」
と一瞬思うほどには現実です。
-----
そしてVRのAVです。("ここから本論" by 江端)
先述のとおり、「VRをしばらく装着していると仮想現実が現実化」します。
僕はワクワクしながら「紗倉まなのVRAV」を購入し視聴を開始しました。
部屋の中でベッドに仰向けになっていたところ(この瞬間、僕も体勢を「座り」から「仰向け」に変えました)、部屋のドアが開き、紗倉まなが入ってきました。
自分にのしかかり、キスをしてきます。
(その際、相手の唇が少しずれるくらいなら脳が補正してくれます。)
また、立体音声でささやかれるのも、主観視点もあいまって「紗倉まながそこにいる」と錯覚させるものに十分なものでした。
しかし、ついに紗倉まなが自分のズボンを下ろしたとき、その光景のせいで、途端に自分の脳が仮想現実であることを認識してしまいました。
「俺の脚じゃない」
ズボンを脱がされて出て来た(VR上の)自分の脚が、(実際の)自分の脚より細く、一気に仮想現実であることを突きつけられました。
その後パンツを脱がされたあと、もちろんモザイクが入ってましたが、正直、脚の細さで冷静になれていましたので、そこで特にショックはありませんでした。
-----
その後自分がVRAVを購入する際に気をつけていた点は2つです。
・ズボンを脱がされない(主観側がチャックを下ろすのみの露出)
・陰茎が「模型」(男優の陰茎ではなく陰茎の模型であることにより、抽象度が増すことで脳が騙されやすい)
-----
今は妻もいますので、VRAVを利用することは全くなくなりました(VRを装着したまま自慰行為に励む姿を絶対に妻に見られたくない)が、
―― あれは、自分にとって、エロのパラダイムシフトでした。
====== ここまで =====
以上、非常に秀逸なレポートを、ありがとうございました。
「pythonで遺伝的アルゴリズム(GA)を実装して巡回セールスマン問題(TSP)をとく」の交叉(部分的交叉)のアルゴリズムをgolangで書き直してみた
今、GAのアルゴリズムを使った動的ルーティングのライブラリ化を試みているのですが、Geneに複雑な(というか、面倒くさい)メカニズムを組み込む為に、四苦八苦しています。
これまで私は、「順序交叉」という手法を用いてきたのですが、昨夜(の深夜)この方式では、私のやりことができないことが判明し、現在、一から作り直しています(2ヶ月分がふっとんだ感じがします)。
で、こちらのpythonで遺伝的アルゴリズム(GA)を実装して巡回セールスマン問題(TSP)をとくのページの「実装例: partial_crossover」の部分を参考させて頂いて、golangで表現してみました。
package main
import (
"fmt"
"math/rand"
)
func main() {
//var sliceA = []int{-1, 1, -1, 2, 6, 3, -1, 4, -1, 5}
//var sliceB = []int{1, -1, 2, 3, -1, 4, -1, 5, 6, -1}
var sliceA = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
var sliceB = []int{1, 3, 5, 7, 9, 0, 2, 4, 6, 8}
fmt.Println("before sliceA:", sliceA)
fmt.Println("before sliceB:", sliceB)
sliceA, sliceB = partial_crossover(sliceA, sliceB)
fmt.Println("After sliceA:", sliceA)
fmt.Println("After sliceB:", sliceB)
}
// 交叉:2つの遺伝子をランダムな位置で交叉させる
// def partial_crossover(parent1, parent2):
func partial_crossover(sliceA []int, sliceB []int) ([]int, []int) {
//num = len(parent1)
length := len(sliceA)
//cross_point = random.randrange(1, num-1)
cross_point := rand.Intn(length-2) + 1
//cross_point = 3
fmt.Println("length:", length, "cross_point", cross_point)
//child1 = parent1
sliceA1 := make([]int, length)
copy(sliceA1, sliceA)
//child2 = parent2
sliceB1 := make([]int, length)
copy(sliceB1, sliceB)
//for i in range(num - cross_point):
for i := 0; i < length-cross_point; i++ {
//target_index = cross_point + i
target_index := cross_point + 1
//target_value1 = parent1[target_index]
//target_value2 = parent2[target_index]
target_value1 := sliceA[target_index]
target_value2 := sliceB[target_index]
//exchange_index1 = np.where(parent1 == target_value2)
//exchange_index2 = np.where(parent2 == target_value1)
var exchange_index1, exchange_index2 int
for k := 0; k < length-cross_point; k++ {
if target_value2 == sliceA[k] {
exchange_index1 = k
break
}
}
for k := 0; k < length-cross_point; k++ {
if target_value1 == sliceB[k] {
exchange_index2 = k
break
}
}
//child1[target_index] = target_value2
//child2[target_index] = target_value1
//child1[exchange_index1] = target_value1
//child2[exchange_index2] = target_value2
sliceA1[target_index] = target_value2
sliceB1[target_index] = target_value1
sliceA1[exchange_index1] = target_value1
sliceB1[exchange_index2] = target_value2
}
return sliceA1, sliceB1
}
このコーディングで正解なのか分かりません(私が、バグを仕込んでいる可能性大)。
また検証した結果、私の考えている遺伝子配列(染色体の中に、遺伝子"*"を使う)では、使えないことが分かりました。
この方式では重複する遺伝子は使えないからです(というか、"*"を使う遺伝子配列は、普通ではない)。
もし利用を予定されている方は、十分に検証されることをお勧めします。
以上
golangの無限for{}ループでブンブン回しているルーチンに、チャネルでちょっかい(割り込み)かける方法
GAを使った推論エンジンを、無限ループで回し続けながら(止めないで)、変数の変更やら、パラメータの変更を突っ込みたいんだけど、その「割り込み」方法が思いつきませんでした。たしか select, caseを使ったやり方があって、defaultの使い方がキモだったようなものがあったような気がして、ちょっとテストプログラム書いてみましたところ、動いたみたいなので、メモの残しておきます。
// go run main2.go
package main
import (
"fmt"
"time"
)
var ch1 chan interface{}
var ch2 chan interface{}
func main() {
ch1 = make(chan interface{}) // チャネルの初期化
ch2 = make(chan interface{}) // チャネルの初期化(ここでは使っていない)
go loop()
for i := 0; i < 5; i++ {
time.Sleep(3 * time.Second) // 3秒待つ
fmt.Println("send")
ch1 <- i
}
}
func loop() {
for {
time.Sleep(1 * time.Second) // 1秒待つ
fmt.Println("loop")
//チャネルからメッセージの到着を確認する
select {
case i := <-ch1:
fmt.Println("ch1:", i)
case <-ch2:
fmt.Println("ch2")
default:
//fmt.Println("No value")
}
}
}
で、この最後の"default"をコメントアウトすると、selectが無限待ちになってしまう(チャネルからメッセージが飛んでこないとロックしてしまう)ので注意して下さい。
コメントアウトした結果↓
# チャネルからイベント入ったら、初期設定のルーチンまで強制的に飛ばしてしまおうと思っているのですが、golangに、"goto"ってあるのかなぁ・・・