2019/01/01

[c/c++][CERT]同期用プリミティブ

C言語の学び直しシリーズ。


POS03-C. volatile を同期用プリミティブとして使用しない
https://www.jpcert.or.jp/sc-rules/c-pos03-c.html

えっっ??
スレッドでvolatileしとけばなんとかなると思っていたけど、そうでもないってことか?
いや、中身を読まずに雰囲気だけで判断してはいかんな。


アトミック性は、mutexなどで同期を取ればよかろう。

私としては、volatileで別スレッドから書き込まれた変数を参照する、ということでよく行うのだが、これは「可視性」と呼ばれるものだと思う。

逐次性・・・は、よくわからん。
メモリ操作が同じ順番で見える、というのは何だろうか?


私の期待しているvolatileは、該当する変数に対してのアクセスを最適化しない、というものだ。
ループで監視するとき、その実行単位の中で変更する処理がなければ最適化したくなるだろうが、そうせずに直接メモリにアクセスする、とか。

ただ、私がやってきたのは組み込みがほとんどなので、コンパイラも組み込み向けがほとんどだった。
だから、volatileはスレッドというよりも、割り込みハンドラに対してのものだ。
そういう知識を元にしているので、OSとかスレッドとかになると、すったりなのだ。


というわけで、文章を読んでもよくわからんし、コードを見ていこう。


まず、最初の違反コード。
これはflagという変数をvolatileでも何でも無く宣言していて、while()の条件として参照しているだけだ。
だから、コンパイラとしてはwhile()の実行前にflagを評価し、それをメモリか何かに保存するだけだろう。

こういう使い方を「同期用プリミティブ」と呼ぶのだろうか?


そして次の違反コード。
これは、flagをvolatileにしただけ。
こうすると、flagに関しての最適化が行われないので、while()では毎回flagを直接アクセスするし、wakeup()が呼ばれればメモリが更新され、while()を抜けるだろう。


それだけでは満足できないらしい。
最後の適合コードでは、volatileでのフラグ参照ではなく、mutexを使っている。
「使用しない」というのは、そういうことか。。

ここではaccount_balanceという変数を複数からアクセスさせたくない、という前提があるのだろう。
だから、flagでwhile()監視するとか、そういうのがそもそも関係が薄い話なのだな。


書込みする方と読み込む方が必ず一方向しかないなら、クリティカルセクションじゃなくても済むだろう。
フラグが立つのを待ってから処理する、とか。
まあ、そういうのは状況によって変わるから、無難に行くならクリティカルセクションか。


フラグ方式にして不安としてあるのが、アトミックにアクセスできない場合だ。
CPUのバス幅より大きかったり、ビット処理をしたりすると、1回ではアクセスできまい。
そうなると、変更している途中に別のスレッドにディスパッチしたりしまうと、まずかろう。

フラグでビットを1つ立てる方だったらそこまで神経質にならんでいいかもしれんが、値を判定するだったら怖いな。
前にやらかしたのは、時計だ。
時計用の変数を1秒間隔のスレッドで更新し、別のところでは変数をアクセスするようにしていた。
で、1秒スレッドで書き換えるところをmutexしていなかったので、不具合として「希に時計の評価がおかしい」という最悪の現象が出てしまったのだ。

フラグでも、32bit環境で64bit変数の2つビットを立てるとなると、ビット位置によっては2回アクセスしないと終わらないので、同じようなことがあり得るだろう。
ああ、こわいこわい。。。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。