排他制御の必要性

[戻る]
pav777 2009/05/26(火) 13:33:48
初めまして、pav777と申します。

複数のスレッドにて1つのint型変数に、書込み/読込みする際、排他制御は必要なので
しょうか?

スレッド構成は以下の通りです。

スレッドA:書込み専用
スレッドB:読込み専用
スレッドC:読込み専用
スレッドD:読込み専用

複数のスレッドで書きこむ可能性がある場合は、排他する必要はあるかと思いますが、
書き込むスレッドが1つの場合、排他する必要があるのかわからず質問しました。

ご回答よろしくお願い致します。

επιστημη [E-Mail] [HomePage] 2009/05/26(火) 13:39:31
> 書き込むスレッドが1つの場合、排他する必要があるのか

CPUが一気に全てを書き込める/読み込めるか否かが問題です。
一連の書き込み動作中に読み込みが割り込む/読み込み動作中に書き込みが割り込むと
データの一貫性が崩れます。

Jason 2009/05/26(火) 14:49:42
一般的から言えば、問題ないです。
しかし、各読み込むスレッドにとって、読み込むシーケンスをよく確保するため、排他制
御が必要だと思います。
排他しないで、予定通り処理シーケンスを従っていない場合も発見したことがあります。

επιστημη [E-Mail] [HomePage] 2009/05/26(火) 16:14:52
読んでる最中に他の読み込みスレッドが割り込むのを心配してるのかな?
そいつは別に問題ないですが。
# readers-writer-lock てーやつやね。

ITO 2009/05/26(火) 21:25:44
 ボタンのイベントやタイマーやエディットボックス等のイベントによりスレッドが
実行するようになっていて、スレッド同士ぶつからない保障があるのなら排他制御は
いりません。
 サーバ等のソフトのようにみんな同じタイミングで動く可能性のある場合は排他制御
が必要です。
 

ITO 2009/05/26(火) 21:33:39
ん?
書込み専用スレッドを常にイベント待ち状態にして、
 読込み専用スレッドが動いてないときにイベントがONになるようにするのが
いいかと思います。
 又は、書き込み終了フラグを各スレッド毎に設けてフラグがONのときは読込み
直すとかですね。
僕は後者かな。

subaru 2009/05/27(水) 01:55:53
επιστημηさんと同様の回答になってしまいますが
書込みと読込みが同時に起こってはいけないので基本的に排他は必要です。

ただ32ビットプロセッサで32ビット変数1つだけにアクセスする場合や
64ビットプロセッサで64ビット変数1つだけにアクセスする場合など
特定の条件下ではソフト側で同期を取らなくても、
物理メモリ操作のレベルで結果的に排他になっているというだけです。

32ビットプロセッサで64ビット変数にアクセスする場合には
上位32ビットと下位32ビットに操作が分かれるので一貫性は保証されません。

bun 2009/05/27(水) 09:01:15
私の方は現実的観点から回答したいと思います。
皆さんのおっしゃるとおり、1つのint型変数限定なら、結果として問題ないでしょう。
問題はそこです。                                   ~~~~~~~~~~

仮に、1つのint型変数というのが、データベースの主キーのようなものだとします。
具体例として、社員番号を想定します。
最初の設計では、社員番号は全社を通してユニークな連番でした。
ところが、ある日、分社化を進めるにあたり、従来の社員番号を改め、会社毎に社員番
号を割り振る形に変更になりました。
結果として、データベースの主キーは[会社ID + 社員番号]になります。

もし、結果として問題ないことだけを利用して、排他処理を省略しており、
そのことを知らない後継者が、この仕様変更を担当することになったらどうでしょう?

int nStaffNum;       // 社員番号
  ↓
struct TStaffNum
{
  int nCompanyId;    // 会社ID
  int nNumber;       // 社員番号
};

もし、書き込みスレッドが会社IDを書き込み、社員番号をまだ書き込んでいない状態で
読み込みスレッドが読み込みに来たら、赤の他人と誤認識する(もしくは該当者なし)と
なるわけです。

ちょっと前にはやった[マーフィーの法則]によれば、こういうのは危惧じゃなく、
近い将来、必ず起きるものとして、あらかじめ対策しておくべきと私は考えます。

pav777 2009/05/27(水) 13:29:07
皆さん、ありがとうございます。

書き込む際に情報が少なく申し訳ありませんでした。

今回のシステムでは、「スレッドA:書込み専用」の処理速度も求められており、各スレ
ッドで共有するデータをなるべく減らすよう設計したのですが、
上記int型変数1つはどうしても共有する必要がありました。

CPUの動作として、int型変数(4バイトの場合)に値を代入した場合、途中まで(例え
ば1バイトまで)書きこんでいる最中に、別スレッドがその変数を読み込むようなこと
が起きるのか、わからず質問をしました。
また、いつもであれば複数スレッドのデータ共有時は、必ず排他制御をかけているので
すが、読込スレッドの処理内容や数により、書込みスレッドの速度が低下する可能性が
あるため、なるべく不必要な処理は実装したくないと言う思惑もありました。

ITO 2009/05/28(木) 21:25:26
うーん、
  書込むための変数を二つ用意して、データ保存用と表示用に分ける。
  読込み専用スレッドは、表示用を使う。
そうすればうまくいくと思います。

 心配なら、書き込み時にフラグを立てて読み込めないようにすればいいと思います。
または、書き込み終了時にイベントをONにする。
読込み専用スレッドはそのイベント待ちにするとか、まだまだやりようがあると
思います。
 

PATIO 2009/05/29(金) 10:30:39
スレッドで処理しているのであれば同一プロセス内の排他ですし、
クリティカルセクションでやればそこまで負担も無いのではないかと
思うのですけれど、それすら問題になるくらいシビアな話なんでしょうか。
グローバル領域に置いておいて、クリティカルセクションで排他なら
そこまで足は引っ張らないのではと思うのですけれど。

書き込まれている内容からだけでは厳しい性能を要求されているのだろう
程度しか伝わってこないので何ともいえませんけれど。

tetrapod 2009/05/29(金) 11:37:54
全部ひっくりかえしちゃうようだけど

最近はあまり見なくなったマルチ CPU マシン(マルチコア CPU ではなく)で、
その共有メモリの値が両 CPU のL1/L2キャッシュに別々に乗っているような場合に
単純に mov 1命令で排他なしにうまく動くんだったっけ?
そのための Interlocked**** だったような気がするんだが。

まあ Readers-Writer-Lock つまり1書き込みN読み込みだったら問題ないか。
Writer 側 CPU で書いた値が Reader 側 CPU に届くまでに遅延がかかるだけだし。
(メインメモリに Cache Write-Back されるまで届かない)

PATIO 2009/05/29(金) 13:17:33
ハードウエアに期待してしまうのであれば、
期待に沿えるハードウエア上で無いと動作保証は出来ないと言う事になるかと。
この辺の動作保証を何処まで厳密にやるの?と言う話かな。

個人的には性能的な問題がないなら排他を掛けてソフト側でちゃんと制御する方が
安全だと思うし、ハード側の事情に左右されない方が良い気はします。

但し、今のマシンでちゃんと動くなら特に問題なしと考えるならそれも道かと。
そこまでしか動作保障はしませんで済むならと言う話になりますけれど。

ここの所の判断は、それぞれの事情とかもあると思うので一概にこれと言う話は
出来ないと思います。
どこまで保証するのかはそれぞれの会社の考え方もあるでしょうし。

subaru 2009/05/29(金) 15:11:41
>tetrapodさん
なるほど。そのような環境での動作は別に考慮がいりそうですね。

.NET Framework の Thread.MemoryBarrier メソッドの解説にもそれっぽいことが書かれ
てます。
>MemoryBarrier は、複数の Intel Itanium プロセッサを使用しているシステムなど、
>ウィーク メモリ オーダリングを採用したマルチプロセッサ システムでだけ使用します。

MSのサイトにもロックなし時の考慮点が書かれたページがありましたのでご参考まで。
http://msdn.microsoft.com/ja-jp/library/bb310595(VS.85).aspx

ITO 2009/06/01(月) 19:58:12
うーーん、
 PATIOさんの意見どおり、クリティカルセクションで十分ですね。
 「そこまでしなくても」って思っていたのですが、処置が確実ですね。

 ハードウエア依存の話もありますが、突き詰めていくとデバイスドライバー
を作らないといけないところまで行きついてしまいます。
 PATIOさんの意見にもあるとおり、ひとまずクリティカルセクションまでで
考えておいた方がいいです。


 

傍観です 2009/06/02(火) 00:35:14
私にはさっぱり分からないレベルになっていますが
専門知識が多すぎて質問者が口出しできる話では
なくなっていませんか?

皆様、それ程の知識があれば比較出来るサンプルコードなりを
提示すれば良いと思うのです。

PATIO 2009/06/02(火) 10:19:22
質問内容から、質問されている方はスレッドやら排他制御に関する知識が
ある程度ある方だと判断しました。
ハードウエアよりの話になるとさすがに深い話になっていると思いますが、
クリティカルセクションで排他を掛けるというレベルであれば、
既に検討されていると考えています。
(実際に設計したという記述もありますし)

クリティカルセクションというキーワードも出ているわけですし、
Web上で検索をかければ、サンプルソースも見つけられると思いますよ。

pav777 2009/06/02(火) 10:51:12
皆さん、たくさんの回答ありがとうございます。

先週末から外出しがちで中々返事を書けず申し訳ありません。
詳細な事は書けませんが、今回のプログラムは厳しい性能(処理速度)を求められてい
ます。
このため、不必要な処理があれば1つでも減らしたいと思っております。
今までは、「念のため」や「とりあえず」と言う意図で処理を実装していたこともあり
ましたが、今回の件で不必要な処理は実装しないと言う考え方もあるのだと実感しまし
た。ただ、今回のケースの排他制御がCPUの動作などを含め、不必要かどうか判断できず
書き込みました。

また、動作保障の件ですが、特定のハードウェアに限定しても構いません。

何か良い意見があれば引き続き書込みお願いします。

bun 2009/06/02(火) 11:36:41
> このため、不必要な処理があれば1つでも減らしたいと思っております。
事情は察しました。
であれば、結果として不要な処理を削除することも賛成です。

ただし...
その上で私の意見を述べておくと、やはり、排他処理はとりあえず実装してみる
方がいいと思います。
実装して動作確認が終わったら、コメントアウトしてしまうのです。
で、コメントアウトの理由もコメントにします。
「処理速度アップのため排他処理を省略しました。」

これで、未来に起きるであろう(特定のハードウェア以外での)不具合発生時に、
すみやかに対処することができます。

subaru 2009/06/02(火) 13:31:59
>詳細な事は書けませんが、今回のプログラムは厳しい性能(処理速度)を求められてい
>ます。
>このため、不必要な処理があれば1つでも減らしたいと思っております。

パフォーマンスが問題になっているのであれば
まずはInterlocked系APIを使う場合とロックなしの場合とで
どの程度差があるかを計測。
わずかなオーバーヘッドがどうしても気になるなら
排他なしにするのも一つの手でしょう。

私も仕様変更時のバグ混入や、実行環境変更時の暴走のリスクを考えると
オススメはできませんが・・・

>今回のケースの排他制御がCPUの動作などを含め、不必要かどうか判断できず
>書き込みました。

int変数一つだけであればメモリ操作での一貫性については問題ないと思います。
問題は環境によって読み書きの順が保障されないことですが
これも前回示したリンク先の内容を信じるなら
Windows + VC++2005以降のコンパイラであれば
volatileキーワードだけで保障されるのかな(?)
#書き込みスレッド1つだからそもそも要らない?

>また、動作保障の件ですが、特定のハードウェアに限定しても構いません。

よほど特殊な事情がない限りそういうことはしないので
実際に確認までしてくれる人はなかなかいないかも。
むしろpav777さんが調べて確認できたことをここに
フィードバックしてほしいです。

ITO 2009/06/02(火) 14:16:06
>今回のプログラムは厳しい性能(処理速度)を求められています。
どのぐらいですか?
スレッド中処理できる時間は約200mSです。
 約200mSの処理が終了した後、スレッドを終了し約200mS間はOSに明け渡さなければ
いけません。

基本的な仕様ですから、マシンの性能によって変わるかも知れません。
リアルタイム性が必要となるならばそこのところを考慮しないとまずいと思います。

処理スピードも、μS以内を維持したいとなるとデバイスドライバーを作ることも
考えないとまずいと思います。

tetrapod 2009/06/02(火) 14:40:57
そういう言い方は正しくないと思うが・・・少なくとも誤解を招くと思う。
・スレッドを終了させるって文言がまずダメ
・200msec って話も必ずしも正しくない
・リアルタイム性って文言も「何に対する」って主語が無くて意味が不明

UI スレッドでは、各1つのメッセージに対する処理を可能な限り速く終わらせて
OSに処理時間を戻す必要がある、なら正確だけど
(処理時間を譲っているだけで、スレッドを終了させているわけではない)
(ユーザ操作に対するレスポンスを維持したいため)

ワーカースレッドでOSに処理を戻さないまま100%CPUを使い続けても
誰も文句言わないし問題ない。
(ユーザー操作に対するレスポンスは落ちるが計算処理のリアルタイム性はあがる)

んで、本題のほうは
・ int 1つ
・ Writer x1 + Reader xN
であるなら「結果論として」排他不要でおK。

ITO 2009/06/02(火) 14:46:05
 ・スレッドを終了させるって文言がまずダメ
    そうですね注意します。
・200msec って話も必ずしも正しくない
 

・リアルタイム性って文言も「何に対する」って主語が無くて意味が不明

ITO 2009/06/02(火) 15:10:59
途中で終わってしまいました。。。。。。

> ・スレッドを終了させるって文言がまずダメ
    そうですね注意します。
>・200msec って話も必ずしも正しくない
>・リアルタイム性って文言も「何に対する」って主語が無くて意味が不明
  確かに200mSと決めたらまずいですね。
  マシンの性能もOSも変わっているし、
  ただ、
    「絶えず連続でデータをやり取りすることが出来ない」。
  ということをいいたかっただけです。

>UI スレッドでは、各1つのメッセージに対する処理を可能な限り速く終わらせて
>OSに処理時間を戻す必要がある、なら正確だけど
>(処理時間を譲っているだけで、スレッドを終了させているわけではない)
そうですね、「処理時間を譲っている。」が正しいです。

>(ユーザ操作に対するレスポンスを維持したいため)
処理よっては、OSの処理時間確保のため中断される場合があります。
スレッド優先順位を変更すればいいのかも知れませんが、
>今回のプログラムは厳しい性能(処理速度)を求められています。
という話だったので書きました。

yoh2 [E-Mail] 2009/06/03(水) 02:07:52
> int変数一つだけであればメモリ操作での一貫性については問題ないと思います。
> 問題は環境によって読み書きの順が保障されないことですが
> これも前回示したリンク先の内容を信じるなら> Windows + VC++2005以降のコンパイラ
であれば
> volatileキーワードだけで保障されるのかな(?)

そのひとつの変数がそれだけで独立して使われるならvolatileのみで大丈夫でしょうね。
リンク先の

| x86 の CPU および x64 の CPU の場合でも、他の書き込みに対して
| 書き込みの順序が変更されたり、他の読み取りに対して読み取りの
| 順序が変更されたりすることはありませんが、読み取りの順序が
| 書き込みに対して変更されることはあります。

という書き込みから、そのひとつの変数が、他の変数への参照を制御するフラグと
して使われるようなら、メモリバリアをうまく使わないと問題ありそうです。

「ひとつのint型整数 (便宜的にxとします) については、ひとつの書き込みと
複数の読み込みが同時に行われるけど半端な状態にはならないから大丈夫。(ここまでは
OK)
他の変数はxがフラグとなってくれるから同期の問題は解決するね。」
と思ってしまうと痛い目に遭うんじゃないかな? と。

余談ながら、
http://www.linuxjournal.com/article/8211/
http://www.linuxjournal.com/article/8212/
で、何故こんなことが起こるのか、というのと、有名所のプロセッサ毎の挙動が
割と分かりやすく解説されています。(ただし英語)
Linux関連サイトの記事ではありますが、プロセッサ周りの話なので、Windows
であっても事情は同じですね。

PATIO 2009/06/03(水) 13:29:06
実際に実測してどの程度の影響があるのかを確認した後の判断になるかと
思います。排他処理に関しては動作保障の観点から行くとあるべきというのが
私の考えです。但し、処理速度の要求上どうしても省かざるえないのであれば、
指定した環境であれば、原理的に大丈夫である事という条件をつけて
外すと思います。いずれにせよ、エイジングテストをしてもこの手の不具合を
必ず見つけると言うのは不可能ですし、そこから先は考え方一つだと思います。

ITO 2009/06/06(土) 13:19:41
うーん、
 実際に測ってみてないんですが、排他処理にかかる時間って気になるものなので
しょうか。どうでしょうか?存知の方お願いします。
 僕は、些細な時間だと思っています。
 その時間さえ許されない処理とはどのようなことをやるかは不明ですが、
WINDOWS環境でも厳しいのでは?と思ってしまいますが、どうでしょうか?
僕が、速度的に気になるのはディスクに書き込むときですね。
「マップトファイル」や「CMemFile」等でメモリーに書込み、処理が終了した時点で
ディスクに書込むってのはどうでしょうか?
 pav777さんもすでに考慮に入れているかもしれませんが、こういった観点から
もう一度振り返ってみるのも大切だと思います。


 

wclrp ( 'o') 2009/06/06(土) 15:37:08
そんなに気になることは無いと思うけど頻度によるでしょ。

それとは関係ないけど面倒だよね。
抜けがあっては意味ないし、
全部一つのミューテックスで止めてたら効率悪そうだから工夫したり、
でもうっかりするとデッドロックになるし。

先に待ちに入ったのに後から待ちに入った方が
ミューテックス獲得するとかでなにかと後回しにされるスレッドがあるとしたら
どういうセオリーでプログラミングするのがいい方法なんだろう。
// XPやくあっどこあならそういう状況になりにくいんだろうな。

subaru 2009/06/06(土) 18:53:04
リンク先の内容から引用するとWindowsでのパフォーマンスの目安は
*  MemoryBarrier: 20 〜 90 サイクル
* InterlockedIncrement: 36 〜 90 サイクル
* クリティカル セクションの獲得または解放 : 40 〜 100 サイクル
* ミューテックスの獲得または解放 : 約 750 〜 2500 サイクル

となっているようです。

名前なしミューテックスとクリティカルセクションは
クリティカル領域のロック用途であれば効果は同じなので
特に理由がない限りクリティカルセクションの方が
よく使われると思います。
また用途にもよりますがフラグやカウンタなどはInterlockedXxxが
適していることも多いです。

>先に待ちに入ったのに後から待ちに入った方が
>ミューテックス獲得するとかでなにかと後回しにされるスレッドがあるとしたら
>どういうセオリーでプログラミングするのがいい方法なんだろう。
クリティカル領域の期間が長いときは問題になることもありそうです。
今回の例ではアクセスが局所的すぎて不向きですが、場合によっては
.NET Framework の ReaderWriterLock クラスや ReaderWriterLockSlim クラス
のようなものも検討されると思います。
Windows Vista からはネイティブで slim reader/writer ロックのAPIがサポートされて
います。
http://msdn.microsoft.com/ja-jp/magazine/cc163405.aspx

古いスレッドには追加発言できません。