2022/06/25

[vbox] 全画面とディスプレイの電源を入れる順番

VirtualBox にて、ホスト環境は Windows10 、ゲストOS で Ubuntu という環境で作業することが多い。
地味に困っていたのが、全画面表示するとなぜか対象になるディスプレイが固定されないという現象だ。

うちの PC はマルチディスプレイ環境で、構成はこうなっている。

image

3つもいらんやろう、といわれそうだが、USB の DisplayLink モニタがあったのでつないでいるのだ。
TeraTerm などを立ち上げておくのにちょうど良かったし。
なお接続方法は、1番が DisplayPort、2番が HDMI、3番が DisplayLinkである。

 

VirtualBox のゲストOS としてはシングルディスプレイで動かしているのだが、全画面表示させると、なぜか 3番になることがほとんどだったのだ。
希望としては 1番で、2番でもまあいいや、くらいなのになぜか 3番の一番小さい(800x600)になるのだ。
非常に困る。やむなくウィンドウの最大表示を使っていたのだが、ちょっととはいえ小さいウィンドウなのが気に障るのだ。

 

ようやく気付いたのは、PCに電源を入れるときの順番によって違いがあるということだ。
私の場合、ディスプレイの電源を手動でオフにしている。
そして、本体の電源を入れて、ディスプレイをオフにしたままパスワードを入力してからディスプレイの電源を入れている。手順が変わらないので表示があってもなくてもいいや、と。

しかし、USB機器は接続したままなので、最初に 3番のモニタに表示が行われる。そのあとで電源を入れたディスプレイに表示が行われることになるのだが、おそらくこの順番が影響している。
検証はしていないが、先にディスプレイの電源を入れてから本体の電源を入れるようにすると今のところ 1番に全画面表示されている。

よかったよかった。

2022/06/19

[golang] 空文字列の []byte キャストは nil ではない

ちょっとだけ不安になったので確認しておこう。

 

package main

import (
    "fmt"
)

func main() {
    x := []byte("")
    if x == nil {
        fmt.Printf("nil\n");
    } else {
        fmt.Printf("not nil\n");
    }
}

$ go run .
not nil

よかった、認識は間違えてなかった。
扱いとしては []byte{} のときと同じかな。

初期値だったり nil を代入しない限りは nil にならないという認識だ。
でないと struct{} なんかも nil になってしまって区別が付かなくなるはずだ。
ただ、 struct{} が必ず別の値になるとは限らないという記述をどこかで見かけた気がする。。。

2022/06/12

[golang] chan で待つ (3)

2番目の記事で疑問になっていたところを解消しておこう。

hiro99ma blog: [golang] chan で待つ (2)
https://blog.hirokuma.work/2022/06/golang-chan-2.html

のこちら。

その後で A が終わって待ち解除になると B 待ち状態になるが、既に処理が終わった B についてどうなるのかわからん。

についてだ。


まず、勘違いしていた件。

 

done := make(chan struct{}, 3)

こう書いたとしても、

done[0] <- struct{}{}

のようには書けないということだ。
make() は capacity を設定するものだから len() ではなく cap() を使って容量を取ってくるのだった。

 

それを踏まえて。

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Printf("start\n")
	done := []chan struct{}{
		make(chan struct{}),
		make(chan struct{}),
		make(chan struct{}),
	}
	go func() {
		fmt.Printf("goroutine start - 1\n")
		time.Sleep(10 * time.Second)
		fmt.Printf("goroutine done - 1\n")
		done[0] <- struct{}{}
		fmt.Printf("goroutine sent - 1\n")
	}()
	go func() {
		fmt.Printf("goroutine start - 2\n")
		time.Sleep(5 * time.Second)
		fmt.Printf("goroutine done - 2\n")
		done[1] <- struct{}{}
		fmt.Printf("goroutine sent - 2\n")
	}()
	go func() {
		fmt.Printf("goroutine start - 3\n")
		time.Sleep(3 * time.Second)
		fmt.Printf("goroutine done - 3\n")
		done[2] <- struct{}{}
		fmt.Printf("goroutine sent - 3\n")
	}()
	fmt.Printf("waiting...\n")
	<-done[0]
	fmt.Printf("done - 1\n")
	<-done[1]
	fmt.Printf("done - 2\n")
	<-done[2]
	fmt.Printf("done - 3\n")
	fmt.Printf("all done\n")
}

実行。

$ go run .
start
waiting...
goroutine start - 3
goroutine start - 1
goroutine start - 2
goroutine done - 3
goroutine done - 2
goroutine done - 1
goroutine sent - 1
done - 1
done - 2
done - 3
goroutine sent - 3
all done

時間の経過が見えないが、「goroutine done - 1」以降は一気に出力されている。
「sent」のログは全部出ていないが、これはその前にプロセスが終了したからだろう。

まあ、これなら前回の最後に書いた for でぐるぐる回して待つ方がシンプルに見えるな。

Blogger に StackEdit からアップするも画像に悩む

普段、ブログにアップする際は Windows の OpenLiveWriter を使っている。
ただ、Microsoft Store だったり GitHub の exe に上がっていたりするのは最新バージョンではない。最近の(というわけでもないが) Gooble Blogger には使えなかったと思う。
自分でビルドして使っていたのだが、新しくセットアップした PC にはそれをしていない。
Blogger の管理画面でエディタを使うことはできるのだが、コードを貼り付けるても見やすくない。
面倒なので、古い PC でアップしていたのだが、そこまでしてブログを書きたいかといわれると悩ましいし、かといってこういうのを書かなくなると老化が進みそうで怖い。

そんな感じでブログエディタをどうしたものか悩んでいたのだが、 StackEdit で連携させることができるというのを偶然知った。
私のブログは README.md の延長みたいなものだから、コードが見やすく貼り付けられて、あとは画像を貼り付けられればよい。


今日はそうやってブログを書いていた。
問題は画像だ。
StackEdit はマークダウンで書くことができるだけなので、画像については範囲外だ。画像ファイルのリンクがあれば貼り付けられる。
しかし、Blogger でいつも使っていた Google Photo だと直接画像ファイルにならないのでマークダウンの画像貼り付け表記ではうまくいかない。

例えば、これは OpenLiveWriter から画像を貼り付けてアップしたときに作ってくれたリンク&画像だ。

https://drive.google.com/uc?id=1V5UE1X-xNM9F3XqVJrOpbuxc4eXqMwft

ああ、貼り付けて分かったけど Photo じゃなくて Drive なんだ。
Drive のファイルからリンクを作ってみるとこうなった。

https://drive.google.com/file/d/1V5UE1X-xNM9F3XqVJrOpbuxc4eXqMwft/view?usp=sharing

うーん、 file/duc?id= に変換して /view 以降は削除する?
大した作業ではないが、来週の私、いや明日の私もきっと忘れているな。
画像のリンクだけなら OpenLiveWriter の方が楽だな。

[golang] chan で待つ (2)

golang の goroutine は、確か実装依存だったと思う。
Linux だったらきっと pthread なんだろうな、と思っていたが、そうではなくて自前で並列処理を作っていたような記憶がある。
まあ、使う方としてはそこまで意識をしないけど、OS 無しだったり独自OS だとどうなるのか気になるね。


さて、前回は 1つのチャネルで 1つの処理が終わるのを待つのを確認した。処理結果がいらない場合にわざわざ struct{}{} を返すので、別に bool とかでいいんじゃないとも思ったが、本に struct{}{} を返すよう書かれているので、そういうお作法と考えるのが良いだろう。値を返す、ということ自体が結構めんどうな処理になりそうだしね。

では複数の goroutine が全部終わるのを待つ場合も考えよう。
make() を使うので第2引数に待ちたいだけの数を設定して待てばよさそうな気がしたのだが、for で待つにしてもどの順番で処理が終わるか分からないから、make() で 3つ作って順に 処理A, B, C に割り当てたとすると、A を待っている間に B, C が先に終わってしまうこともあるだろう。その後で A が終わって待ち解除になると B 待ち状態になるが、既に処理が終わった B についてどうなるのかわからん。


 それはともかく、make()で複数の chan を作るのは「バッファ有りチャネル」として紹介されていた。
本の説明では 3つ作っている。そして 4つめは「送信文は待たされます」と書かれている。ということは chan は受信待ちだけでなく送信待ちというものもあるということか。
ただ、これは対象がスライス全体を与えているからそうなるのだと思う。スライスの各要素で送受信するようにしたらバッファ無しのチャネルと同じになるはずだ。

あるいは、スライス全体にすることで「早い者勝ち」になるようだから、それをループで回数だけ待てばよいはずだ。

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Printf("start\n")
	done := make(chan struct{}, 3)
	go func() {
		fmt.Printf("goroutine start - 1\n")
		time.Sleep(10 * time.Second)
		fmt.Printf("goroutine done - 1\n")
		done <- struct{}{}
	}()
	go func() {
		fmt.Printf("goroutine start - 2\n")
		time.Sleep(5 * time.Second)
		fmt.Printf("goroutine done - 2\n")
		done <- struct{}{}
	}()
	go func() {
		fmt.Printf("goroutine start - 3\n")
		time.Sleep(3 * time.Second)
		fmt.Printf("goroutine done - 3\n")
		done <- struct{}{}
	}()
	for range done {
		<-done
	}
	fmt.Printf("done\n")
}

実行。

$ go run .
start
goroutine start - 3
goroutine start - 1
goroutine start - 2
goroutine done - 3
goroutine done - 2
goroutine done - 1
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /home/xxx/golang/src/chan/main.go:30 +0x125
exit status 2

あれ、ダメなんだ。
for の回数を固定させるとエラーは起きないようなので range done

package main

import (
	"fmt"
	"time"
)

func main() {
	const eventNum = 3
	fmt.Printf("start\n")
	done := make(chan struct{}, eventNum)
	go func() {
		fmt.Printf("goroutine start - 1\n")
		time.Sleep(10 * time.Second)
		fmt.Printf("goroutine done - 1\n")
		done <- struct{}{}
	}()
	go func() {
		fmt.Printf("goroutine start - 2\n")
		time.Sleep(5 * time.Second)
		fmt.Printf("goroutine done - 2\n")
		done <- struct{}{}
	}()
	go func() {
		fmt.Printf("goroutine start - 3\n")
		time.Sleep(3 * time.Second)
		fmt.Printf("goroutine done - 3\n")
		done <- struct{}{}
	}()
	for i := 0; i < eventNum; i++ {
		<-done
	}
	fmt.Printf("done\n")
}

実行。

$ go run .
start
goroutine start - 3
goroutine start - 1
goroutine start - 2
goroutine done - 3
goroutine done - 2
goroutine done - 1
done

本には、

  • len() は現在バッファされている要素の個数を返す
  • cap() はバッファ容量を返す

とある。
range donelen(done) と同じルールならダメそうだ。
本によると、 range でチャネルを回すのはチャネルに対して送信されたすべての値を受信するときと書かれているので len()と同じなのだろう。

いろいろやってみらんとわからんもんやね。

[golang] chan で待つ

オライリーさんからGo言語の本が出たので悩み中。

O’Reilly Japan - 実用 Go言語
https://www.oreilly.co.jp/books/9784873119694/

まだプログラミング言語Goも読み進めていないので早すぎるという気もするが、お仕事で使っている以上実用の本もほしいのが正直なところだ。
まあ、そのうち買うんだろうね。


さて、本題。

処理Aがあって、その処理の後に処理Bを行いたいことがあった。
それだけなら続けて書けば良いだけだったのだが、コンテキストが同じ状態で処理Aと処理Bを実行できないようで(自分で作ってない処理なのでよくわからん)、処理A は goroutine してやらないと処理Bが必ず失敗していた。
つまり、処理Aが非同期になったのだ。

今は処理Aが終わりそうな時間だけtime.Sleep()で待ってから処理Bを実行させているのだが、そういえば非同期処理の待ち合わせがあったなあ、と思い出した。
channelだ。
まあ、goroutine と channel はプログラミング言語Goでも同じ章で説明してあるくらいだしセットで覚えるものよね。

チャネルでの待ち合わせ

チャネルはmake()で作り、close()で閉じられる。使わなければよいだけなのだろうけど、明示的に「これ以降は使えない」というフラグを立ててくれるそうだ。

make(chan 型)で作るが、型は戻り値と思っておけばよさそうだ。
TypeScriptでいえばPromise<型>みたいな感じかな。
単に待ち合わせたいだけだったら戻り値はいらないのだが、そういうときはstruct {}を使えばよいようだ。golangにvoidはないのだね。

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Printf("start\n")
	done := make(chan struct{})
	go func() {
		fmt.Printf("goroutine start\n")
		time.Sleep(10 * time.Second)
		fmt.Printf("goroutine done\n")
		done <- struct{}{}
	}()
	<-done
	fmt.Printf("done\n")
}

実行。

$ go run .
start
goroutine start
goroutine done
done

10秒開けているのだけど、終わってからdoneが実行されている。

2022/06/05

[golang] go.mod の pseudo-version は go mod tidy で修正される

相変わらず go.mod で悩んでいた。

hiro99ma blog: [golang] go.mod の pseudo-version の調べ方がわからん
https://blog.hirokuma.work/2021/11/golang-gomod-pseudo-version.html

hiro99ma blog: [golang] go.mod の pseudo-version
https://blog.hirokuma.work/2021/12/golang-gomod-pseudo-version.html

なんとなく pseudo-version の書き方は分かったものの、面倒であることに変わりは無い。

開発中は replace で相対パス指定しておけばごまかせるのだが、GitHub Actions で linter を仕込んでいたりするとエラーになって気持ちが悪い。 Actions の方も相対パスで使えるようにすることができるのかも試練が、そこまでするくらいだったら go.mod を修正した方が楽そうだが、修正が面倒。。。

 

何か pseudo-version を自動で取ってくるツールがないか調べていた。

Mastering Go Modules Pseudoversions | JFrog GoCenter
https://jfrog.com/blog/go-big-with-pseudo-versions-and-gocenter/

真ん中当たりに「How to Fix Improper Pseudo-versions」という項目があり、修正の仕方が載っていた。なんと go mod tidy でうまいことやってくれるそうだ。

書き方は、pseudo-version を書く代わりに git の commit-id を書くだけ。これで go mod tidy すると pseudo-version に書き換えてくれた。 replace のところでもよいようだ。

今までの苦労は一体。。。

 

まあよい。できれば良かろうなのだ。