ミューテックス - みる会図書館


検索対象: Modern C++ Design
14件見つかりました。

1. Modern C++ Design

322 付録 A 最小限のマルチスレッド・ライブラリ ミューテックスの基本的な機能は , Acquire と Re1ease です。 ( 共有変数といった ) リソースへの排他的な アクセスを必要とする各スレッドは , こういったミューテックスを獲得します。たった 1 つのスレッドのみが ミューテックスを獲得できるのです。いったん , あるスレッドがミューテックスを獲得した場合 , Acquire を起 動したその他全てのスレッドは待機状態でプロックされるのです ( Acquire 関数から制御が返ってこないわけで す ) 。ミューテックスを所有しているスレッドが Re1ease 関数を呼び出した場合 , スレッドのスケジューラは同 じミューテックスに対して待機状態にあるスレッドの 1 つを選択し , そのミューテックスの所有権を該当スレッ ドに引き渡すのです。 この結果 , 見かけ上はミューテックスによってデバイスが 1 つづつ順番にアクセスされるようになります。っ まり , コード中の mtx. Acquire() 呼び出しと mtx. Re1ease() 呼び出しに囲まれた部分は , mtx オプジェクト に関してアトミックなものとなるわけです。そして , mtx オプジェクトを獲得しようとするその他全ての試み は , アトミックな操作が完了するまで待たなければならないのです。 これにより , スレッド間で共有させたいリソース毎にミューテックス・オプジェクトを 1 っ割り当てることに なります。特に重要な共有リソースとして , C+ + のオプジェクトを挙げることができるでしよう。こういったリ ソースを用いた非アトミックな操作毎に , まずミューテックスを獲得し , 最後にミューテックスの解放を行わな ければなりません。また , 特に重要な非アトミック操作として , スレッド安全性のあるオプジェクトに対する非 const メンバ関数を挙げることができます。 例えば , Deposit ( 入金 ) と Withdraw ( 出金 ) といった関数を提供する BankAccount ( 銀行口座 ) という クラスがあると考えて下さい。こういった操作は , トランザクションに関する付加的な情報を記録する等の , double のメンバ変数に対する加算や減算以上の処理を行います。もし BankAccount が複数のスレッドからア クセスされるのであれば , 2 つの操作は確実にアトミックなものでなければならないのです。これは以下のよう mtx-.ReIease() ; 出金トランザクションを実行する mtx— . Acquire ( ) ; void Withdraw(doub1e amount , const char* user) mtx— . Re1ease() ; . 入金トランザクションを実行する mtx-.Acquire ( ) ; VOid Deposit (double amount , const char* user) public : class BankAccount にして実現することができます : Mutex mtx_ private : 既にお気付きのように ( まだお気付きになっていないかもしれませんが ) , Acquire の発行後に呼び出してい

2. Modern C++ Design

A. 5 オブジェクト指向プログラミングにおけるロックのセマンティックス 323 る Re1ease が失敗した場合 , 困ったことが起こります。ミューテックスのロック後 , それがロックされたまま になるわけです。つまり , それを獲得しようとしている他の全てのスレッドが , 永遠に待ち続けることになるの です。上記のコードを処理している途中で , 例外が発生し , そのままリターンしてしまうことがないよう , 細心 の注意を払って Deposit と Withdraw を実装しなければならないのです。 こういった問題を和らげるため , 多くの 0 + スレッド API ではミューテックスを用いて初期化される Lock オプジェクトを定義しています。 Lock オプジェクトのコンストラクタによって Acquire が呼び出され , そのデ ストラクタによって Re1ease が呼び出されるわけです。このようにして , Lock オプジェクトをスタック上に割 り当てておけば , 例外の発生有無とは関係なく , Acquire と Re1ease のペアが正しく実行されることになるの です。 可搬性から来る理由によって , Loki は自身のミューテックスを定義していません。おそらく , あなたは既に固 有のミューテックスを定義したマルチスレッド・ライプラリを使用していることでしよう。このため , 同じ機能 を重複させるのは不適当なのです。その代わり , Loki はミューテックスを用いて実装した , より高水準なロッ ク・セマンティックスを用いていいます。 A. 5 オブジェクト指向プログラミングにおけるロックのセマンティックス 同期化オプジェクトは , 共有リソースと関連づけられます。そして , オプジェクト指向プログラムでは , リ ソースはオプジェクトになります。従って , オプジェクト指向プログラムでは , 同期化オプジェクトはアプリ ケーション・オプジェクトと関連づけられることになります。 このため , それぞれの共有オプジェクトは , 同期化オプジェクトを統合し , B kAcco t の例のように , すべ てのメンバ関数内で適切にロックを行わなければならないのです。これが , マルチスレッドをサポートするオプ ジェクトを構成する正しい手段です。こういったオプジェクト毎に同期化オプジェクトを 1 つ採用する方法は , オブジェクト・レベルのロックと呼ばれています。 しかし , オプジェクト毎にミューテックスを 1 つ格納すると , そのサイズやオーバーヘッドを無視することが できないような場合がしばしば出てきます。このような場合には , クラス毎にミューテックスを 1 つだけ保持す る同期化戦略を採用することができます。 以下の例 , Stri Ⅱ g クラスを考えてみましよう。 St て ing オプジェクトに対してロック操作を実行する必要が 出てきました。しかし , string のサイズが大きくなり , コピーにもコストが掛かるようになってきたため , 各 string にミューテックス・オプジェクトを保持させるのは得策ではなくなりました。こういった場合 , すべて の string に対して静的なミューテックス・オプジェクトを使用することができます。 string オプジェクトに 対してロック操作が実行された場合 , その操作は全ての stri Ⅱ g オプジェクトに対するロック操作をプロックす るわけです。こういった戦略は , クラス・レベルのロックと呼ばれています。 Loki は , ThreadingMode1 ポリシーとして , C1assLeve1LockabIe と ObjectLeve1LockabIe という 2 つ の実装を定義しています。これらは , それぞれクラス・レベルのロックとオプジェクト・レベルのロックに対す るセマンティックスをカプセル化したものです。その概要は以下の通りです : template く typename HOSt> class C1assLeve1Lockab1e public :

3. Modern C++ Design

A. 4 ミューテックス class SomeThreadingM0de1 public : 321 / / あるいはプラットフォームが規定する別な型 typedef int IntType ; static IntType AtomicAdd(v01ati1e IntType& lval , IntType val) ; static 工 ntType AtomicSubtract (volatile IntType& lval , 工 ntType val) ; AtomicMu1tip1y, AtomicDivide, AtomicIncrement, AtomicDecrement 等も 同様の定義となる static void AtomicAssign(v01ati1e IntType& lval , 工 ntType val) ; static void AtomicAssign( 工 ntType& lval , volatile IntType& val) ; このプリミテイプは , 変更を行うための値を最初のパラメータから取得し ( 非 const の参照で volatile を用 いて引き渡しを行っている点に注意して下さい ) , 他のオペランド ( 単項演算子でない場合 ) を 2 番目のパラメー タから取得しています。各プリミテイプは v 。 latile な代入先のコピーを返します。実際の操作結果をそのまま 取り扱うことができるため , 戻り値が有効利用できるわけです。以下のように記述して , 操作後の v 。 latile 値 を扱った場合 : volatile int counter ; SomeThreadingM0de1 く Widget> : : AtomicAdd(counter , 5 ) ; if (counter = 10 ) AtomicAdd への呼び出しと if ステートメントの間で , 他のスレッドが c 。 unte てを更新できるため , 加算直後 の counter の値を検証することはできません。たいていの場合 , 特定の AtomicAdd の直後で counter の値に 何が入っているのかを知りたいはずなのです。この場合には , 以下のように記述します : if (AtomicAdd(conter, 5 ) 1 の コピー操作でさえも非アトミックな場合があるため , AtomicAssign 関数も 2 つ必要となるわけです。例え ば , あなたのマシンのバス幅が 32 ビットで , long が 64 ビットである場合 , long の値のコピーにはメモリ・ア クセスが 2 回発生するのです。 A. 4 ミューテックス マルチスレッド環境下におけるオペレーティング・システムのスケジューラは , ある種の同期化オプジェクト を提供しなければならないということが , EdgarDijkstra によって証明されています。マルチスレッド・アプ こういったものが必要不可欠なのです。 リケーションを正しく記述するためには , ミューテックスは , 共有リソースの順序だったアクセス手段をスレッドに対して提供するための , 基本的な同 期化オプジェクトです。このセクションでは , ミューテックスの概念を定義しています。 Loki の他の部分では , ミューテックスを直接扱っていません。その代わり , ミューテックスを用いて簡単に実装することができる , よ り高水準な同期化手段を定義しています。 ミューテックス ( rn 厩 ) とは , 相互排他 (MutualExclusive) の略であり , 同期化用のプリミテイプ・オプ ジェクトの機能を解説するための用語です。ミューテックスによって , スレッド群は相互に排他的なリソースの アクセスを行えるようになるわけです。

4. Modern C++ Design

156 / / mutex- はミューテックス・オブジェクト / / Lock はミューテックスを管理します Lock guard(mutex-) ; if ( !p 工 nstance-) plnstance— return *plnstance— new Sing1eton ; 第 6 章 Singleton の実装 Lock クラスは古典的なミューテックス・ハンドラです ( ミューテックスの詳細については付録を参照して下さ い ) 。 Lock のコンストラクタは , ミューテックスのロックを行い , デストラクタはミューテックスのロック解除 を行います。そして , mutex- がロックされている間 , 同じミューテックスのロックを行おうとする他のスレッド は待たされるのです。 これによって , 競合条件を一掃することができるわけです。つまり , スレッドが p 工Ⅱ st ce ーに代入している 間 , 他のスレッド全ては guard のコンストラクタで停止するわけです。他のスレッドがロックを行う時には , p 工Ⅱ st ce ーは既に初期化されており , 全てはスムーズに流れることになるのです。 しかし , 正しい解決策が常に人を惹きつけるものとは限りません。効率性の低下という不具合があるからで す。競合条件は 1 回しか発生しない , つまり寿命中に 1 度しか発生しないにもかかわらず , 工 nst ce を呼び出 す度に , 同期オプジェクトに対するロックとロック解除が引き起こされるのです。こういった操作は , 単純な if ( ! p 工Ⅱ stance ー ) のテストよりも , 常にコストの掛かるものとなります。 ( 今日のシステムでは , 条件分岐と クリティカル・セクションに対するロック時間は , 数桁のオーダーで違っているのです。 ) このオーバーヘッドを避けるために , 以下のようなコードを考えてみます : Sing1eton& Sing1eton : : 工 nstance ( ) if ( !p 工 nstance-) Lock guard(mutex—) p 工 nstance— new SingIeton ; return *plnstance— これでオーバーヘッドは消え去りますが , 競合条件が帰ってきてしまいます。最初のスレッドが if のテストを 通過し , 次の同期化を行うコードに差し掛かった時点で OS のスケジューラが割り込みを行い , 制御を他のス レッドに引き渡したとします。そして 2 番目のスレッドも if のテスト自身を通過し ( ポインタが null である ため当たり前です ) , 同期化コードを実行し , 一連の処理を完了します。この後 , 最初のスレッドに再度制御が 戻ってくれば , 同じように同期化コードを実行しますが , 時既に遅し , またしても 2 つの Si Ⅱ glet 。 n オプジェ クトが構築されてしまうわけです。 これは答えのない難問のように見えますが , 実際には非常に簡単でエレガントな答えがあるのです。それが DoubIe-Checked Locking パターンです。 このアイディアは簡単なものです。条件をチェックし , 同期化コードに入った後で , 再度条件をチェックす

5. Modern C++ Design

A. 6 オプションの volatile 修飾子 Sing1eThreaded クラス・テンプレート C1assLeve1Lockab1e ObjectLeve1Lockab1e 325 表 A. 1 ThreadingM0de1 の実装 その型の全てのオプジェクト ) はロックされます。 ラス Lock によってミューテックス ( および暗黙のうちに クラス毎に 1 つのミューテックスが格納されます。内部ク クラス・レベルのロック・セマンティックスとなります。 暗黙のうちに該当オプジェクトも ) はロックされます。 ます。内部クラス Lock によってミューテックス ( および ます。オプジェクト毎に 1 つのミューテックスが格納され オプジェクト・レベルのロック・セマンティックスとなり ままで生成されます。 スレッド戦略を使用しません。 Lock と ReadLock は空の セマンティックス void Withdraw(doub1e amount , const char* user) Lock lock(*this) ; . 出金トランザクションを実行する で見ることができます。 価され , Sing1eThreaded の場合に単なる Widget へと評価されるのです。 V01ati1eType の使用例は , 第 6 章 では , C1assLeve1Lockab1e と ObjectLeve1Lockab1e の場合に VoIati1eType は volatile Widget へと評 このため , Loki では内部クラス V01ati1eType を定義しています。 SomeThreadingMode1 く Widget> の内部 は指定するべきではないのです。 しかし , v 。 latile の指定によってコンパイラはある種の最適化を抑止するため , シングルスレッド・モデルで 0 + は , マルチスレッドで共有する変数をそれぞれ指定するため , 型の修飾子 v 。 latile を提供しています。 A. 6 オプションの volatile 修飾子 章 (Sing1eton の実装 ) で使用しています。 ThreadingMode1 ポリシーは , 第 4 章 ( 小規模オプジェクトの割り当て ) , 第 5 章 ( 汎用のファンクタ ) , 第 6 スレッド・モデルを修正するだけで , 設計上の変更を簡単に行えるようにもなります。 ンタックス上の整合性も保つことができます。このためコード記述は , マルチスレッド環境を対象としておき , また , ダミー・インタフェース SingIeThreaded によって , 一貫したインタフェースが提供されるため , シ るロックとロック解放のペアは , 言語によって常に正しく実行されることが保証されるのです。 これで , 途中で例外が発生し , 関数が終了してしまっても問題は起こらなくなります。ミューテックスに対す

6. Modern C++ Design

7.14 全てを 1 つに 199 複数のスレッドが , 互いにリンクしあっているスマート・ポインタを破棄する場合 , そのデストラクタがアト ミック ( 他のスレッドによって割り込まれないもの ) となっていなければならないのは明らかです。さもなけれ ば他のスレッドは , 例えば prev ーー > next ーに対する更新と next ー→ prev ーに対する更新が行われる間のタイミン グで SmartPtr のデストラクタに割り込むことができます。そうすると , そのスレッドは破壊されたリストを操 作することになるわけです。 同じ考え方が SmartPtr のコピー・コンストラクタと代入演算子に対しても適用されます。こういった関数は 所有者リストを操作するため , アトミックでなければならないのです。 大変興味深いことに こではオプジェクト・レベルのロック・セマンティックスを適用することができませ ん。付録では , ロックの戦略をクラス・レベルとオブジェクト・レベルの戦略に分割しています。クラス・レベ ルのロック操作では , 操作中は指定したクラスにおける全てのオプジェクトをロックします。オプジェクト・レ ベルのロック操作では , 該当操作対象のオプジェクトのみをロックします。前者のテクニックでは占有メモリが 少なく ( クラス毎にミューテックスが 1 つ存在するだけ ) なりますが , パフォーマンス上のポトルネックが発生 します。後者はサイズが大きく ( オプジェクト毎にミューテックスが存在する ) なるものの , より高速になり ます。 私たちは , ある 1 つの操作によって , 追加・削除対象のカレント・オプジェクト , 所有権リスト中にある直前 のオプジェクト , 直後のオプジェクトという 3 つのオプジェクトを操作することになるため , スマート・ポイン タに対してオプジェクト・レベルのロックを適用することができないのです。 オプジェクト・レベルのロックを採用する場合 , オプジェクト毎にミューテックスが 1 つ必要となる点に目を 向けて下さい。各オプジェクトに対して動的にミューテックスを 1 つ割り当てることはできますが , そうした場 合 , 参照カウント方式よりも参照リンク方式の方が優れている主なメリットを台無しにしてしまうのです矢昭 0 3 彡・ハ、、 リンク方式が魅力的なのは , 自由領域を一切使用しないためだったはずです。 代替策として侵入的なアプローチを使用することができます。つまり , 指しているオプジェクトにミューテッ クスを保持し , スマート・ポインタはそのミューテックスを操作するのです。しかし , 十分効果的な代替策 , つ まり参照カウント方式のスマート・ポインタが存在するため , こういった機能を提供する意味は無いのです。 要約すれば , 参照カウント方式や参照リンク方式を用いるスマート・ポインタは , マルチスレッドの問題によ る影響を受けることになります。スレッド・セーフな参照カウント方式にはアトミックな整数操作が必要とな ります。スレッド・セーフな参照リンク方式にはミューテックスが必要となります。なお , SmartPtr はスレッ ド・セーフな参照カウント方式のみを提供しています。 7.14 全てを 1 つに もう少しです ! こからが面白いところです。今までは , それぞれの問題を個別に扱ってきました。そして , ついに全ての成果を集めて smartptr の実装を作り上げる時が来たのです。 こで使用する戦略は第 1 章で解説したポリシーに基づくクラス設計です。決定的な解決策とならない設計上 の各観点は , ポリシーとなるのです。 SmartPtr クラス・テンプレートは , 各ポリシーを個別のテンプレート・ パラメータとして受け取ります。 SmartPtr はこういったテンプレート・パラメータを全て継承し , 対応するポ リシーが状態を格納できるようにするわけです。 SmartPtr における様々な観点を列挙することにより , 以前のセクションを振り返ってみましよう。各バリ 工ーションが以下のポリシーとなるのです。

7. Modern C++ Design

索引 195 ー 197 指しているオプジェクト・レベルの一 - 参照カウント方式と一 - , 197 ー 198 一定時間の - ー , 306 ー 309 マルチメソッド - ーライプラリ , 317 ー 326 ミューテックスとーー 321 ー 323 ー - 批判 , 318 ー 319 スマート・ポインタとーー , 195 ー 199 参照追跡と一 3 198 ー 199 ーーのチャンク , 87 ー 94 - アロケータの動作 , 84 ー 86 RMW ( 読み込み - 更新 - 書き出し ) 操作 , 320 メモリ ミューテックス , 156 ー 158 , 321 ー 323 引数と一 , 301 ー 306 ーーのまとめ , 314 ー 316 ーの必要性 , 279-280 ーの基本的解説 , 277 ー 316 対数ダブル・ディスパッチャと - 対称性と一 , 288 ー 291 型の判定の二重化 , 280 ー 282 291-301 337 ヒープ - ー 戻り型 共変の一 131 ー 133 , 201 ーー 202 239 ・一の変換 , 121 ー 122 汎用のファンクタとーー 要求のチェイニング , 129 呼び出し可能工ンティティ , 109 ー 110 リソース・リーク , 142 リドウ , 133 粒度の高いインタフェース , 236 例外 , 221 連想コレクション , 216 ロック 121 ー 122 ーーのセマンティックス , 323 ー 325 クラス・レベルの一一 , 323 オプジェクト・レベルの一 - , 323 Double-Checked Locking パターン 155 ー 158

8. Modern C++ Design

10.6 10.7 Visitor ジェネリック・コンポーネントのまとめ 10.8 第 11 章 11.1 11.2 11.3 11.4 11.5 11.6 11.7 11.8 11.9 11.10 11.11 11.12 11.13 11.14 付録 A A. 1 A. 2 A. 3 A. 4 A. 5 A. 6 A. 7 A. 8 参考文献 マルチメソッドとは ? マルチメソッド 総括 . ノヾリュ、一ション、 ポリシーとしての BasicDispatcher と BasicFastDispatcher 定数時間のマルチメソッド : 最高速度 引数の変換 : static-cast か dynamic-cast か ? ファンクタのダブル・ディスパッチ FnDispatcher と対称性 対数ダブル・ディスパッチャ カまかせのディスパッチャにおける対称性 カまかせのアプローチを自動化する 型の判定を二重化する : カまかせに行う方法 . どういった場合にマルチメソッドが必要なのか ? 総括 . セマフォ , イベント , その他もろもろ オプションの volatile 修飾子 オプジェクト指向プログラミングにおけるロックのセマンティックス ミューテックス 整数型に対するアトミックな操作 Loki のアプローチ マルチスレッド批評 最小限のマルチスレッド・ライブラリ ダブル・ディスパッチャのまとめ 総括 . 将来に向けて . 索引 V11 . 271 . 274 . 275 277 . 278 . 279 . 280 . 282 . 288 . 291 . 297 . 298 . 301 . 306 . 309 . 310 . 312 . 314 317 . 318 . 319 . 319 . 321 . 323 . 325 . 326 . 326 327 329

9. Modern C++ Design

324 class Lock public : Lock() ; Lock(Host& Obj ) ; template く typename HOSt> class ObjectLeve1Lockab1e public : class Lock public : Lock(Host& obj) ; 付録 A 最小限のマルチスレッド・ライブラリ この場合 , Lock がミューテックスのロックを保持することになります。 2 つの実装の違いは , オプジェク ト T を引き渡さなければ ObjectLeve1Lockab1e く T > : : Lock を構築できないという点です。これによって , ObjectLeve1Lockab1e はオプジェクト毎のロックを実現するわけです。 ネストしたクラス Lock によって , Lock オプジェクトの寿命がある間は , 該当オプジェクト ( あるいは C1assLeve1Lockab1e の場合は該当クラス全体 ) がロックされることになります。 アプリケーションでは , Th て eadingM 。 del に対する実装のいずれかを継承します。その後 , 内部クラス Lock . 入金トランザクションを実行する Lock lock(*this) ; VOid Deposit (double amount , const char* user) public : class BankAccount : public ObjectLeve1Lockab1e く BankAccount> 以下の例で概略を示しているように , 同期化メンバ関数も非常に簡単に定義できます : めています。 正確なロック戦略は , どの ThreadingMode1 を継承したかに依存します。表 A. 1 に , 利用可能な実装をまと class MyC1ass : public CIassLeveILockab1e く MyCIass> を直接使うことになります。例えば :

10. Modern C++ Design

317 付録 A これによってマルチスレッ マルチスレッド・プログラムは , 同時に複数の実行点を保持します。現実的には , 最小限のマルチスレッド・ライブラリ ンの設計を支援することができます。 用いたものよりも高水準のものであり , これによってオプジェクト指向によるマルチスレッド・アプリケーショ い面を見てみると , この付録で定義している同期化のコンセプトは , 従来からあるミューテックスとセマフォを 的なオペレーティング・システムが提供している豊富な機能と比べると随分と見劣りしてしまいます。ただ明る Loki のスレッド機能は , スレッド安全性のあるコンポーネントを提供することのみに注力しているため , 現代 化を見つけだすことに注力しています。 しまうだけでしよう。ここでは , マルチスレッドに対応したコンポーネントを記述できるだけの最小限度の抽象 で , ついでのように完全なスレッド・ライプラリの考察を行ったとしても , それは無益なものであり , 破綻して こではそういったものを扱っていません。本書中 る包括的な紹介は , それ自身魅力的な分野とも言えますが , の , しつかりとした土台となるツールとテクニックを提供しています。マルチスレッド・プログラミングにおけ この付録では , 0 + でマルチスレッド対応のポータブルなオプジェクト指向アプリケーションを記述するため きないのです。 マルチスレッドを使用することが多くなってきているため , 怠惰の名の下にマルチスレッドを無視することはで した場合 , ほとんどのものがマルチスレッドでは使いものにならなくなります。 ) 最近のアプリケーションでは , このため , 本書で提供しているコンポーネントもスレッドの問題を無視することはできません。 ( 実際に無視 が自身のスレッドを用いていない場合であっても , 組み込んでおかなければならないのです。 リを用いて作業を行う場合 , ライプラリもスレッドを考慮する必要があります。こういった機能は , ライプラリ チスレッドは , アプリケーションの設計にも影響を与えるのです。スレッドが複数存在する環境下で , ライプラ は , プログラミングが非常に難しいものであり , さらにデバッグはより難しいものとなっています。その上マル を記述しなければならないのです。しかし残念なことに , ユーザにとって喜ばしいマルチスレッドというもの ューザは砂時計のカーソルが表示されることを望んでいないため , プログラマはマルチスレッド・プログラム ら , 文法の検証を行うことができるわけです。 象をユーザに与えることができるのです。例えば , ワード・プロセッサは , ユーザにテキストを入力させなが 時間を割り当てることが繰り返されます。マルチスレッドによって , 同時に複数の作業が行われているような印 c のによって短い時間間隔毎に各スレッドを分割し , スレッドを一時中断させ , 別なスレッドにプロセッサ ピュータで , マルチスレッドをサポートしたオペレーティング・システムの場合 , タイム・スライシング (time は , 文字通り異なったスレッドによって同時に処理が実行されることもあります。シングルプロセッサのコン ド・プログラムが同時に複数の関数を実行できることを意味しています。マルチプロセッサのコンピュータで