2018年11月11日

[linux][pthread]pthread_cond_wait()を勘違いしていた

(2018/11/11 23:19追記あり)

Linuxでpthreadを使っている。
スレッドがいくつかあって、相手のスレッドに処理要求して、終わるまで待つ、という同期処理が必要になった。
そういうときは、pthread_cond_wait()で待って、pthread_cond_signal()で起こしてもらえば良かろう。


・・・ということはわかったのだが、あまり使い方を読まずに使っていた。
そのせいで、相手のスレッドに要求するスレッドが複数になった場合にmutexがlockするという現象が起きていた。

ちゃんと読まずに使うのはよくない、という反省を込めて記事にしよう。


共有のメモリに処理してほしい内容を通知して、相手は処理したらそのメモリに結果を返す。
処理する方は呼ばれるまで待っているし、呼び出す方のスレッドは終わるまで待たせる。

pthread_cond_wait()は引数にlockしたmutexを取るようだったので、こう考えていた。

  • cond_wait()する直前にmutex_lock()する
  • mutex_loc()するんだから、他のスレッドが同じことをしようとしたらmutex_lock()で待たされる


そして、こういう実装をしていた。
見づらくてすまんが、黄色から右側が処理するスレッドで、左側は要求するスレッドたちだ。

要求するスレッドは、まず要求側のmutexをlockして、メモリに書込み、cond_signal()で相手を起こしたら、自分はcond_wait()で寝る。
処理側は処理が終わったらcond_signal()で起こして、自分はcond_wait()で寝る。

image


これでよかろうと思っていたのだが、何だかよくわからないタイミングでスレッドが固まることが分かった。
pthread_cond_wait()の使い方など疑ってもいなかったのだが、他にあやしい箇所がない。。。


IBM: 一般的なスレッド: POSIX スレッドの説明: 第3回

これの「もう一度、復習を」を見ると、pthread_cond_wait()呼び出しはロックを解除した後にスリープ状態になる、と書かれている。
そ、そんなばかなー!


よく読むと、こういうことらしい。

  • pthread_cond_wait()は、mutexをunlockした後でスリープ状態になる
  • pthread_cond_signal()などが呼ばれると、mutexをlockしてから起き上がる

今回の現象は、pthread_cond_wait()のあとはunlockされているから、別のスレッドがlockできてしまうために起きているのだ。
実装したものがすべてじゃないのね・・・。


そういうことなので、シーケンスを修正した。
明示的に実装していないmutexのlock/unlockは赤い矢印で表した。


image


これなら、mutexのlock/unlockが意図せずクロスすることはないだろう。


いやー、mutexというかpthreadは内部の動きも理解していないといけませんな。
などと書きつつも、これも間違っていたら嫌だなぁ。。。


2018/11/11 23:19追記


やはり・・・間違えていた。
基本的な考え方は悪くなかったのだが、シーケンスが良くない。

描いたシーケンスだと、

  1. 右側のスレッドがcond_wait()する
  2. 左側のスレッドがcond_signal()を投げる
  3. 左側のスレッドがcond_wait()する
  4. 右側のスレッドがcond_signal()を投げる

としていたのだが、2番でsignalを投げた後、左側がwaitに入る前に右側のスレッドがsignalを投げてしまい、待ち人来たらずになってしまうことがあったのだ。


そもそも、cond_wait()の前にmutex_lock()することで何がよいかというと、cond_wait()されることでmutex_unlock()が行われることだ。
これによって、別のスレッドがmutex_lock()して待ち状態になっているものが解除されることで、順番を制御できるのだ。

大ざっぱに書くと、こういう感じか。

image

先にlockして、signalを投げたい方をlockで待ち状態し、waitでunlockすると、相手がsignalを投げるのは必ずwaitした後になるということだ。


修正は1箇所。
左側のスレッドがsignal()を投げた後にlock()していたのを、その前にするだけだ。
そうしておくと、右側のスレッドはsignal()を投げる前にlockしているので、順番が守られる。

image


さあ、これくらいで直ってくれないだろうか。。。

2018年11月10日

[win10]ウインドウの枠を太くしたい2018年秋

定期的に気になる、Windows10のウィンドウ枠が細い問題。
問題というか、仕様なのだろうが、たくさんウィンドウを開いて選択し間違えるたびにイライラするので、個人的には問題なのだ。


以前も試したのだが、Winaero TweakerでテーマをAero Liteにしてみよう。
Windows10は、Homeの1809。
Tweakerのバージョンは、0.12.1.0。


Aero Liteはデフォルトでは有効になっていないので、レジストリ操作などで有効にできたはずだ。
戻すのが面倒そうなので、Winaero Tweakerを使う。

image


まあ、上の画面は変更後なんだけどね。
ウィンドウを重ねた場合は、こう見える。

image


アクティブなウィンドウは文字色が黒で、非アクティブはグレーだ。
変更できたかもしれないが、まあよかろう。


枠の色が黄色っぽいのは、タスクバーのせいだ。
何の話かというと、タスクバーの文字色が黒で固定されるようなのだ。

image

前回も、これが原因でテーマを元に戻している。
Windowsで使える色をそのまま使うと、どれを選んでも見づらいからだ。
私は青っぽい色を使っていたのだが、それだとこの通り。

image


当時はあまり色の選択ができなかったのかもしれないが、今はユーザが色をある程度選べるようになっていたので、自分が許容できる色が選べる。

image


image

(何気なく選んだが、グレーもいいかも)



「ある程度選べる」というのは、明るすぎる色だとWindowsが許してくれないからだ。
(2018/11/23修正:「サポートしない」といっているだけで、選択できました!)

image


なんでWindowsがそういうのを指摘するかというと、ストアアプリのリンク色に反映されるからではなかろうか。
下の図だと「夜間モードの設定」と「Windows HD Color設定」はリンクなのだ。

image


タイトルバーにも反映されるので、そこも考慮すべきだろう。
重ねてみたが、Explorerはグレーだけど、メモ帳やTeraTermはそんなことないな。ストアアプリでもOpenLiveWriterとSkypeは違うし。
そもそも、ストアアプリでタイトル領域が塗りつぶされている方が少ないのか。

image


私では非アクティブなタイトル文字色を変更する方法が分からなかったが、そういうのを探していくのもまたよいだろう。

2018年11月3日

[c]LevelDB C (2)

まずは、以前参考にしたC interfaceのexampleを見る。

サンプルだからだろうが、中身はシンプルだ。

  • openオプション設定
  • open
  • writeオプション設定
  • put
  • readオプション設定
  • get
  • delete
  • close
  • destroy

destroyまですると、DBのディレクトリごと削除するようだった。


気になったのでvalgrindを使ってみると、メモリの解放をしていないところがあった。
あと、getしたものをそのまま\0無しで出力しているので、よろしくない。
そこら辺を修正した。

https://github.com/hirokuma/leveldb-c-example/blob/64f448da1f345f4c94b924ceb2dd9f62725e649e/leveldb_example.c


オプションはそれぞれ内部でmalloc()しているので、それぞれの解放APIを呼ぶことになった。
openのオプションはdestroyでも使っているが、同じleveldb_options_tである必要はないかも。
少なくとも、このサンプルを改造して、openとdestroyでそれぞれleveldb_options_create()を使ったが、特にエラーは出ていない。

c.ccを見ても、options->repを渡しているだけだし、参照してはいるものの保持している感じはしないので、たぶん・・・たぶん大丈夫なんじゃなかろうか。

[c]LevelDB C (1)

いままでLMDBを使っていたが、違うタイプのDBも検討したい。
候補として出てきたのは、以前も調べたLevelDB。
C++で書かれているが、Cのインターフェースもあるので、そちらを見ていこう。


なぜ検討しようかと思ったかというと、LMDBの特徴のいくつかが、いま作っているアプリと噛み合わなくなってきたからだ。

ここのサイトにまとまっていた。
A short guide to LMDB – Kolab Now


"Caveats"の最初に書かれているように、memory mappingされるようになっていて、それがサイズの上限になってしまうのだ。
そんなに大きいデータを扱うつもりがなかったので気にしていなかったのだけど、だんだん扱うサイズが大きくなってきて、とうとうエラーが発生するようになってしまったのだった。
64bitのLinuxで開発していたら気にならなかったのだけど、Raspberry Pi3に持っていくと1.5GBくらいまでしか指定できなかった。
environmentを複数に分ければ確保できるのかもしれないが、そこまでしてLMDBに合わせなくてもいいんじゃないのか・・・。


DBの内容は削除もしていくのだが、「The database file will never shrink」なので、アクションを起こさないと小さくなってくれない。
mdb_env_copy2()でMDB_CP_COMPACTを実行すれば減りそうだが、それをやるにはcopy2した後のenvに切り替えないといかんだろう。
その時間が致命的かどうかが、ちょっと見積もることができていない。


Transactionの範囲がDBではなく、environment単位というのも手痛かった。
1つのDBだけアクセスしたくても、それを含むenvironmentをロックすることになるのだ。
まあ、だからTransactionは手短にするよう書かれているのだろうがね。

それに関して解決できていないのが、mdb_txn_begin()だ。
複数のスレッドからアクセスすることがあるので、先にmdb_txn_begin()したスレッドが優先され、後から呼んだ方はmdb_txn_commit/abort()されるまでロックされるだろう、という想定でいた。
いたのだが、両方ロックされてしまうことがあったのだ。
mdb_txn_begin()前後にログを出しながらtidを見ていたのだが、2スレッド目がmdb_txn_begin()すると両方のログが止まってしまったから、そう判断している。
なんかAPIの使い方を間違えているような気はするのだけど、よくわかってない。


そんなこんなで、LMDBの問題ではなく特徴が生かせなくなっているので、別のDBも見てみようか、と考えたのだった。


もう1つ、LMDB本体とは直接関係ないのだけど、Windows 10の2018年春バージョン以降のWSLでLMDBがうまく動かないというのも後押ししている。
WSLのディスクキャッシュが変わったのが原因だろうということだったので、予想が当たってちょっとうれしいものの、VirtualBoxなどのLinux環境が必須になったのは面倒だ。
1台のWindows10だけ2018年秋バージョンにしてみたが、そっちでも変わらなかった。

2018年10月27日

[vscode]構造体メンバの候補一覧keyはselectNext/PrevSuggestion

Visual Studio CodeをVirtualBoxで立ち上げたUbuntu18.04上で使っている。
Linuxで慣れたエディタがないので助かるのだが、キーバインドが分からずに困っている。
WindowsではAutoHotKeyを動かしたままなので、どうも思った操作ができないようなのだ。


最近困っていたのが、構造体メンバの候補リストが表示されているときに矢印キーの上下で候補が選択できないこと。
デフォルトではできたのかもしれないが、いろいろ変更していたせいか、上下の矢印キーを押すとエディタ画面の方のカーソルが移動してしまうのだ。


あれこれ試した結果、該当する名前は「selectNextSuggestion」「selectPrevSuggestion」だった。
デフォルト値を見ておこう。

image

ああ、デフォルトでは普通のキーに割り当てられているのか。。。
しかし、こんなキーを変更した覚えがないのだが、どの名前がどの機能か分からないときに当てずっぽうで変更していたのかもしれん。


あらゆるキーをカスタマイズできそうな気配はするのだが、名前が分からんとカスタマイズのしようがないな。
調べ方を覚えたいものだ。

2018年10月15日

[c/c++]sizeof忘れでコンパイルエラーにならなかった(当たり前だけど)

なぜか、malloc()だか、そのfree()だかわからないけど、エラーが出ていた。
サイズが足りないのにアクセスしたような感じのエラーだ。


確保するサイズは、sizeof()で求めていた。
TLVじゃないけど、Length + Valueをいくつかつなげたようなサイズを確保したかったのだ。

Lengthは1byte分で、uint8_t型。
その箇所は、uint64_t型をValueに取りたかったので、こんな感じで書いていた(つもりだった)。


int length = sizeof(uint8_t) + sizeof(uint64_t);


しかし、私は最初の方にsizeofを書き忘れていたのだった。。。

int length = (uint8_t) + sizeof(uint64_t);


驚いたことに、これがコンパイルエラーにはならないのだ。
-Wや-Wallつけてるのに、warningも出ない。

01: #include <stdio.h>
02: #include <stdint.h>
03: 
04: int main(void)
05: {
06:     int length = (uint8_t) + sizeof(uint64_t);
07:     printf("len=%d\n", length);
08: }


なんとなく想像がつくだろうが、この結果は「8」だ。
"+sizeof(uint64_t)"をuint8_tでキャストしたことになったのだろう。


なかなか気付きませんでしたなぁ。。。

2018年10月6日

[win10][wsl]defender firewallを設定する

Windows10 1809がリリースされたので、アップデートした。

https://docs.microsoft.com/en-us/windows/whats-new/whats-new-windows-10-version-1809#windows-defender-firewall-now-supports-windows-subsystem-for-linux-wsl-processes

どうやら、Windows Defender FirewallにWSLのプログラムを設定できるらしい。
私はセキュリティに強くないので、Windows Firewall Controlを使って「許可していないアプリは禁止」にしているのだが、WSLの場合はpicoとかいうプロセスがアクセスして、これがどうやっても許可させることができなかったのだ。


やり方は、こちら。
python2.7のIN方向を許可するサンプルが載っていた。

https://docs.microsoft.com/en-us/windows/wsl/release-notes#build-17627-skip-ahead

netsh.exe advfirewall firewall add rule name=wsl_python dir=in action=allow program="C:\users\<username>\appdata\local\packages\canonicalgrouplimited.ubuntuonwindows_79rhkp1fndgsc\localstate\rootfs\usr\bin\python2.7" enable=yes

単純に、Windowsから見たファイルパスを登録するだけのようだ。
残念ながらWindows Firewall Controlは*.exeのようなものしか登録できないようだ。
「セキュリティが強化されたWindows Defender ファイアウォール」(長いよ...)だと追加できるようだが、local/packages以下がダイアログから見えなかったので、Explorerでたどったパスをコピペで登録させた。


まず/usr/bin/wgetを登録。
うん、いける。

では、普段よく使う/usr/bin/aptを登録・・・したのだが、これが通信できない。
拒否しているタイミングを見計らってps auxしてみたところ、/usr/lib/apt/httpというプロセスが立ち上がっていた。
そいつを許可してやるとaptが通るようになった。
やれやれ。


しかし、これは使うのが面倒だ。
当面はWSL使うときだけFirewallを許可する方が楽そうだ。

と、ふとWindows Firewall Controlの遮断ログを見ると、先ほどの/usr/lib/apt/httpが出ているじゃないか!
そうか、picoが前面に出ていたものが、各ファイルになったのだな。
そして、Windows Firewall Controlは自分でルールを追加する場合には*.exeみたいなファイルしか設定できないものの、ログから許可設定するのはOKだった。
これなら便利ですな。

2018年9月16日

[esp8266]RTOS v3.0

かなり久々だが、ESP8266のRTOS_SDK v3.0がリリースされていたので、Windows10のWSLで動かしてみた。

https://github.com/espressif/ESP8266_RTOS_SDK



だいたいこんな感じだった。

sudo apt install libncurses5-dev flex bison gperf
sudo apt install python-pip
sudo pip install pyserial

cd (project_templateまで移動)
make menuconfig
make all


なお、COMポートの設定はこちらを見ながら行った。
chmodで先に変更しないと、"sudo make flash"ではダメだった。

WSLでシリアル通信をする
http://matsuneko22.hateblo.jp/entry/2017/12/09/144803


IO0をLにして起動後にFLASH書込み実行。

make flash


このproject_templateで何が動くのかと思ったら、printf()するだけだった。
baud rateがデフォルトなので74880bpsで送信しているようなのだが、使っているUSBシリアル変換がその速度をサポートしてないので確認できなかった。
まあ、動いてるんだろう。


いずれはesp-idfと同じ形にするのか、統合するのか、ともかくそういう計画らしい。
私が使ってみたかったMbedTLSのchacha20は入ってなかったので、ライブラリのバージョンもesp-idfより古いものがあるようだ。


まあ、socketとかは使えるので、Linuxで書いたプログラムを移植しやすいのは助かるな。

2018年9月3日

[linux][azure]OSディスクサイズを増やした後

会社でAzureを使っている。
月も変わったことだし、少しメンテナンスをしておこうと休日に見てしまったのが間違いだった。。。
ディスクがフルになっているVMがあったのだ。


開発用にVMを立てているだけなので、実はそれほど深刻ではない。
面倒でなければ、VMごと削除して新しく作っても良いくらいだ。
ただ、面倒なので、ディスクを増やすだけで対応させたい。


私はAzureやらAWSやら、そういうものに詳しくない。
なんとなく使えているので、これでいいか、と思っているだけだ。
その程度の知識の人が書いていると思って読み進めていただきたい。


まず、OSディスクサイズを増やすには、VMを停止させる。
そうすると、「設定>ディスク」とクリックして、出てきたOSディスクをクリックした先の画面で変更ができるようになる。
フルになっているものが30GiBになっていたので、ひとまず64GiBにしておいた。


そして保存してVMを起動させる。
で、ここからがよくわからないのだが、なぜかパーティション全体が拡張されるVMと、ディスクだけが拡張されているVMの2パターンが発生するのだ。
OSは、Ubuntu16.04。
gpartedか何かで拡張すればよさそうなものだが、そもそもディスクが空いていないのでインストールもできない。。。
同じようにVMを作ったような気がするけど、何が違うんだろうね?


まっとうな使い方としては、データディスクを追加して、データが大きいものはそっちを使うとかなんだろうかね。
この辺りは勘所が分からん。


仕方ないので、パーティションが拡張されなかったものは、パーティションを追加して対応させることにした。

fdiskでデフォルトサイズのままパーティションを作り、再起動して、mkfs.ext4。


問題は、これからだ。
/etc/fstabをどう書くか。。。苦手なのだ。
こんなのでどうだろうか?

/dev/sda2       /home/xxx/yyyy  ext4    default,discard 0 0

私にとって大切なのは、mountしたディスクにユーザ権限で書き込めるかどうかだ。
が、それ以上に大切なのは、起動するかどうかでs


あ、起動しなくなった。。。


Azureの助かるところは、SSHがだめでもブラウザからログインできることだ。
fstabの設定を消したら、やはりログインできるので、なんかよくないのだろう。

$ sudo mount -t ext4 /dev/sda2 yyyy

これも大丈夫だった。
何が悪いんだ・・・。


はっ、「default」ではなく「defaults」なのか!
無事起動しました。

ユーザからのアクセスも、sudo chownで対応できた。
mountするディレクトリはユーザが作ったから大丈夫かと思っていたけど、mount後にchownしないといかんのかな?

2018年9月2日

[python]pythonの仮想環境

名前はよく聞くし、なんとなく仮想的なものを意味している感じがするのは分かっている。
そんなvirtualenvだが、意識的に使ったことが無い。
使い方の説明でコマンドが書かれていて、そのまま使っている程度だ。


歳を取ってきて覚えるのが面倒になってきているのを実感しているが、それに逆らってみよう。


https://virtualenv.pypa.io/en/stable/

virtualenv is a tool to create isolated Python environments.

isolatedとあるので、分離してくれるのだろう。


pipを使っていたので意識しなかったが、パッケージはどこかにインストールされる。
"site-packages/"というディレクトリらしい。
作っているものによってはバージョンが異なるパッケージを使いたいこともあるだろう。
そういうときの問題を解決できるらしい。


らしい、というのは、私がそういう状況に陥っていないからだ。
まあ、pythonで大したものを作っていないという意味でもあるが。。。


そういえば、最近は提供されるものでpython3を使っていることが多い。
pipがpip3になっているかのように、virtualenvもvirtualenv3なのだろうか?


そもそも、pipはpython2で、pip3はpython3なのだろうか?
python2がインストールされているからpip3になっていて、python3だけだったらpipでよいのだろうか?


https://docs.python.org/ja/3/tutorial/venv.html?highlight=pip#managing-packages-with-pip

ここではpipになっている。
このサイトがそう説明しているなら、そうなのだろう。


https://docs.python.org/ja/3/installing/index.html#installing-index

python3.4からは一緒にインストールされるようになったということは、それ以前はpipを単独でインストールしていることになる。
確かに、apt installした気がする。
Ubuntu16.04からアンインストールしてからpipを使ってみよう。

$ sudo pip install --upgrade pip
The directory '/home/xxx/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
The directory '/home/xxx/.cache/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Collecting pip
  Downloading https://files.pythonhosted.org/packages/5f/25/e52d3f31441505a5f3af41213346e5b6c221c9e086a166f3703d2ddaf940/pip-18.0-py2.py3-none-any.whl (1.3MB)
    100% |████████████████████████████████| 1.3MB 1.1MB/s 
Installing collected packages: pip
  Found existing installation: pip 9.0.1
    Uninstalling pip-9.0.1:
      Successfully uninstalled pip-9.0.1
Successfully installed pip-18.0

うーん、よくわからん。

$ sudo -H pip install numpy
Requirement already satisfied: numpy in /usr/lib/python3/dist-packages (1.11.0)

これはpython3だろう。


ここで、ようやく--versionオプションに気付いた。

$ pip2 --version
pip 9.0.1 from /usr/local/lib/python2.7/dist-packages (python 2.7)

$ pip3 --version
pip 18.0 from /usr/local/lib/python3.5/dist-packages/pip (python 3.5)

$ pip --version
pip 18.0 from /usr/local/lib/python3.5/dist-packages/pip (python 3.5)

python-pipをアンインストールしてもpip2は使えるらしい。が、pipはpython3のものだ。


https://docs.python.org/ja/3/installing/index.html#installing-index

で、このサイトを見ると、仮想環境を作るのはvenvが標準らしい。
virtualenvはサードパーティの代替え案とのことだ。



https://docs.python.org/ja/3.7/library/venv.html#module-venv

見ながら使ってみよう。
Ubuntu16.04にpython3.5.2がインストールされていたから、3.7にしてみる。

https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa
こういうppaって、何も考えずに信用していいのか悩むな。。

tarでやるのが確実だろうけど、pythonはUbuntuのモジュールも使っているようだから、何か怖い。
pythonのバージョンごと仮想化する方法もあるんだろう。

では、カレントディレクトリに作ってみる。

$ python3 -m venv ./env1
The virtual environment was not created successfully because ensurepip is not
available.  On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.

    apt-get install python3-venv

You may need to use sudo with that command.  After installing the python3-venv
package, recreate your virtual environment.

Failing command: ['/home/hiroshi/Python/.env1/bin/python3', '-Im', 'ensurepip', '--upgrade', '--default-pip']

えっ、venvは標準じゃなかったの??
釈然としないが、apt installでpython3-venvをインストールしてやり直す。


フォルダができたので、移動。
そこから何をしたらいいのか分からんかったが、bin/activateをたたけば良いようだ。
https://qiita.com/fiftystorm36/items/b2fd47cf32c7694adc2e

そうすると、コマンドプロンプトの先頭に環境名が出てくるようになった。

~/Python/env1$ source bin/activate
(env1) ~/Python/env1$

試しにnumpyをpipでinstallさせてみた。bin/activate前は「既にインストールされています」というようなメッセージだけだったのだが、bin/activate後はちゃんとインストールされた。


あれ、どうやって元に戻ればいいんだ・・・と思ったら、deactivateというコマンドがあるようだ。
これはbin/ ではないし、whichでも見つからなかった。
でも、実行すると元に戻った。


ここまでやって気付いたが、使っているpython3はpython3.5.2のままだった。
どうも、"python3.7"とやらないとそうなるようだ。
そして、python3.7-venvをインストールしないとvenvが作れなかった。

python3.7でvenvを実行すると、その中の実行もpython3.7になるようだ。
バージョンはそれで保てそうだ。