マルチスレッドコンピュータプログラミングでは、関数が複数のスレッドから同時に呼び出されたりアクセスされたりしても、予期しない動作や競合状態、データ破損が発生しない場合、その関数はスレッドセーフです。[ 1 ] [ 2 ]プログラムが共有アドレス空間で複数のスレッドを同時に実行し、各スレッドが他のすべてのスレッドのメモリにアクセスできるマルチスレッドコンテキストと同様に、スレッドセーフ関数は、すべてのスレッドが意図しない相互作用なしに適切に動作し、設計仕様を満たすことを保証する必要があります。[ 3 ]
スレッドセーフなデータ構造を作るための様々な戦略がある。[ 3 ]
スレッドセーフティのレベル
スレッドセーフティについてはベンダーによって若干異なる用語が使用されていますが[ 4 ]、最も一般的に使用されるスレッドセーフティ用語は次のとおりです。[ 2 ]
- スレッドセーフではありません: データ構造は異なるスレッドから同時にアクセスされるべきではありません。
- スレッド セーフ、シリアル化:すべてのリソースに対して単一のミューテックスを使用することで、複数のスレッドが同時にリソースにアクセスした場合でも、スレッドが競合状態にならないことを保証します。
- スレッド セーフ、MT セーフ:すべてのリソースに対してミューテックスを使用することで、複数のスレッドが同時にリソースにアクセスした場合にスレッドが競合状態にならないことを保証します。
スレッド安全性の保証には通常、様々な形態のデッドロックのリスクを防止または制限するための設計手順や、同時実行性能を最大化するための最適化も含まれます。しかし、デッドロックはライブラリ自体とは無関係に 、コールバックやアーキテクチャのレイヤリング違反によって発生する可能性があるため、デッドロックフリーの保証は常に得られるとは限りません。
ソフトウェアライブラリは、一定のスレッドセーフ性を保証することができます。[ 5 ]例えば、同時読み取りはスレッドセーフであることが保証されているかもしれませんが、同時書き込みは保証されていない可能性があります。このようなライブラリを使用するプログラムがスレッドセーフであるかどうかは、そのライブラリをこれらの保証と整合した方法で使用しているかどうかによって決まります。
実装アプローチ
競合状態を回避してスレッドセーフを実現するため の 2 つのアプローチを示します。
最初のクラスのアプローチは、共有状態を回避することに重点を置いており、次のものが含まれます。
- 再突入性[ 6 ]
- コードを記述する際に、あるスレッドによって部分的に実行されたり、同じスレッドによって実行されたり、あるいは別のスレッドによって同時に実行されたりしても、元の実行を正しく完了できるようにすること。そのためには、状態情報を静的変数やグローバル変数、あるいはその他の非ローカルな状態ではなく、各実行にローカルな変数(通常はスタック上)に保存する必要があります。すべての非ローカルな状態はアトミック操作によってアクセスする必要があり、データ構造は再入可能でなければなりません。
- スレッドローカルストレージ
- 変数は各スレッドが独自のプライベートコピーを持つように局所化されます。これらの変数はサブルーチンやその他のコード境界を越えて値を保持し、各スレッドにローカルであるため、たとえそれらにアクセスするコードが別のスレッドによって同時に実行される場合でも、スレッドセーフです。
- 不変オブジェクト
- オブジェクトの状態は、構築後に変更できません。これは、読み取り専用データのみが共有され、かつ固有のスレッド安全性が確保されていることを意味します。可変(非const)操作は、既存のオブジェクトを変更するのではなく、新しいオブジェクトを作成するように実装できます。このアプローチは関数型プログラミングの特徴であり、 Java、C#、Pythonの文字列実装でも使用されています。(不変オブジェクトを参照)。
2 番目のアプローチは同期に関連し、共有状態を回避できない状況で使用されます。
- 相互排除
- 共有データへのアクセスは、常に1つのスレッドのみが共有データを読み書きできるようにするメカニズムを用いてシリアル化されます。相互排他制御の導入は慎重に検討する必要があります。不適切な使用は、デッドロック、ライブロック、リソース枯渇といった副作用を引き起こす可能性があります。
- アトミック操作
- 共有データへのアクセスは、他のスレッドから割り込まれることのないアトミック操作によって行われます。これは通常、ランタイムライブラリなどで提供される特殊な機械語命令の使用を必要とします。操作はアトミックであるため、他のスレッドがどのようにアクセスしても、共有データは常に有効な状態に保たれます。アトミック操作は多くのスレッドロック機構の基盤となり、相互排他制御プリミティブの実装に使用されます。
例
次のJavaコードでは、Java キーワードsynchronizedによりメソッドがスレッドセーフになっています。
クラスCounter {プライベートint i = 0 ;パブリック同期void inc () { i ++ ; } }C言語では、各スレッドは独自のスタックを持ちます。ただし、静的変数はスタック上に保持されず、すべてのスレッドが同時に共有アクセスします。複数のスレッドが同じ関数を実行している間に、あるスレッドが静的変数の値を変更している最中に、別のスレッドがその値をチェックしている可能性があります。この診断が難しい論理エラーは、ほとんどの場合正常にコンパイルおよび実行されますが、競合状態と呼ばれます。これを回避する一般的な方法の一つは、別の共有変数を「ロック」または「ミューテックス」(mutual ex clusionの略)として使用することです。
POSIX ヘッダーを呼び出す次の C コードでは、関数はスレッドセーフですが、再入可能ではありません。
#include <pthread.h>int incrementCounter () { static int counter = 0 ; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ;// 一度に 1 つのスレッドのみ増分できるようにするpthread_mutex_lock ( & mutex );++カウンター;// 他のスレッドがさらに値を増やす前に値を保存しますint result = counter ;pthread_mutex_unlock ( &ミューテックス);結果を返す; }上記の例では、increment_counterミューテックスを使用して共有counter変数へのアクセスを同期しているため、異なるスレッドから問題なく呼び出すことができます。しかし、この関数が再入可能な割り込みハンドラ内で使用され、ミューテックスがロックされている間に2つ目の割り込みが発生すると、2つ目のルーチンは永久にハングアップしてしまいます。割り込み処理によって他の割り込みが無効化される可能性があるため、システム全体に悪影響を与える可能性があります。
C++11で導入されたロックフリーアトミックを使用すると、同じ関数をスレッドセーフかつ再入可能に実装できます。
stdをインポートします。std :: atomicを使用します。int incrementCounter () { static atomic < int > counter ( 0 );// 増分はアトミックに実行されることが保証されていますint result = ++ counter ;結果を返す; }参照
参考文献
- ^ Kerrisk, Michael (2010). 『Linuxプログラミング・インターフェース』No Starch Press 699ページ、「第31章 スレッド:スレッドセーフティとスレッドごとのストレージ」
{{cite book}}: CS1 メンテナンス: 追記 (リンク) - ^ a b Oracle (2010-11-01). 「Oracle: スレッドセーフ」 . Docs.oracle.com . 2013-10-16閲覧「ある手続きがスレッドセーフであるのは、複数のスレッドによって同時に実行されても論理的に正しい場合である」; 「スレッドセーフのレベル3」
{{cite web}}: CS1 メンテナンス: 追記 (リンク) - ^ a b「マルチスレッド・プログラミング・ガイド:第7章 安全で安全でないインタフェース」 . Docs Oracle . Oracle . 2020年11月. 2024年4月30日閲覧; 「スレッドセーフ」
{{cite web}}: CS1 メンテナンス: 追記 (リンク) - ^ 「APIスレッドセーフティ分類」 . IBM. 2023年4月11日. 2023年10月9日閲覧。
- ^ 「ライブラリのMT安全レベル」 . Docs Oracle . 2024年5月17日閲覧。
- ^ 「再入性とスレッドセーフ | Qt 5.6」 . Qtプロジェクト. 2016年4月20日閲覧。
外部リンク
- Java Q&A Experts (1999年4月20日). 「スレッドセーフ設計 (4/20/99)」 . JavaWorld.com . 2012年1月22日閲覧。
- TutorialsDesk (2014年9月30日). 「Javaにおける同期とスレッドセーフティのチュートリアル(例付き)」 . TutorialsDesk.com . 2012年1月22日閲覧。
- Venners, Bill (1998年8月1日). 「スレッドセーフ設計」 . JavaWorld.com . 2012年1月22日閲覧。
- Suess, Michael (2006年10月15日). 「スレッドセーフをマスターするためのショートガイド」 . Thinking Parallel . 2012年1月22日閲覧。