回答:
ウィキペディアから:
スレッドセーフティは、マルチスレッドプログラムのコンテキストで適用可能なコンピュータープログラミングの概念です。複数のスレッドによる同時実行中に正しく機能するコードの一部は、スレッドセーフです。特に、複数のスレッドが同じ共有データにアクセスする必要性、および共有データの一部に常に1つのスレッドのみがアクセスする必要性を満たす必要があります。
スレッドセーフを実現するには、いくつかの方法があります。
再入:
あるタスクで部分的に実行され、別のタスクで再入力され、元のタスクから再開できるようにコードを記述する。これには、静的またはグローバル変数ではなく、通常はスタックにある、各タスクのローカル変数に状態情報を保存する必要があります。
相互排除:
共有データへのアクセスは、常に1つのスレッドのみが共有データを読み書きできるようにするメカニズムを使用してシリアル化されます。コードの一部が複数の共有データにアクセスする場合は、細心の注意が必要です。問題には、競合状態、デッドロック、ライブロック、飢餓、および多くのオペレーティングシステムの教科書で列挙されているその他のさまざまな問題が含まれます。
スレッドローカルストレージ:
変数はローカライズされているため、各スレッドには独自のプライベートコピーがあります。これらの変数は、サブルーチンやその他のコードの境界を越えて値を保持し、アクセスするコードが再入可能であっても、各スレッドに対してローカルであるため、スレッドセーフです。
原子操作:
共有データには、他のスレッドによって中断できないアトミック操作を使用してアクセスします。これには通常、ランタイムライブラリで利用可能な特別な機械語命令を使用する必要があります。操作はアトミックであるため、他のスレッドがアクセスするデータに関係なく、共有データは常に有効な状態に保たれます。アトミック操作は、多くのスレッドロックメカニズムの基礎を形成します。
続きを読む:
http://en.wikipedia.org/wiki/Thread_safety
フランス語:http : //fr.wikipedia.org/wiki/Threadsafe (非常に短い)
スレッドセーフコードは、多くのスレッドが同時にコードを実行している場合でも機能するコードです。
より有益な質問は、コードがスレッドセーフにならない理由です。答えは、真でなければならない4つの条件があるということです...次のコード(および機械語の翻訳)を想像してください
totalRequests = totalRequests + 1
MOV EAX, [totalRequests] // load memory for tot Requests into register
INC EAX // update register
MOV [totalRequests], EAX // store updated value back to memory
私はブライアン・ゲッツの包括的なJava並行性の定義が包括的であることを気に入っています
「ランタイム環境によるそれらのスレッドの実行のスケジューリングやインターリーブに関係なく、複数のスレッドからアクセスされたときにクラスが正しく動作する場合、クラスはスレッドセーフであり、呼び出しコードの追加の同期やその他の調整は必要ありません。 」
他の人が指摘したように、スレッドセーフとは、1つのコードが複数のスレッドで一度に使用された場合、エラーなしで機能することを意味します。
これは、コンピュータの時間とより複雑なコーディングを犠牲にして行われることもあるため、常に望ましいとは限りません。クラスが1つのスレッドでのみ安全に使用できる場合は、そうする方がよい場合があります。
たとえば、Javaには、ほぼ同等の2つのクラスがStringBuffer
ありStringBuilder
ます。違いは、StringBuffer
スレッドセーフであるため、の単一のインスタンスをStringBuffer
複数のスレッドで一度に使用できることです。StringBuilder
はスレッドセーフではなく、Stringが1つのスレッドのみで構築されている場合(大多数)の場合のより高性能な代替品として設計されています。
それを理解するより簡単な方法は、コードをスレッドセーフではないものにすることです。スレッド化されたアプリケーションに望ましくない動作をさせる2つの主な問題があります。
ロックせずに共有変数にアクセスする
この変数は、関数の実行中に別のスレッドによって変更される可能性があります。関数の動作を確実にするために、ロックメカニズムを使用してそれを防止する必要があります。一般的な経験則は、可能な限り最短の時間でロックを維持することです。
シェア変数への相互依存によるデッドロック
2つのシェア変数AとBがある場合。1つの関数では、最初にAをロックしてから、後でBをロックします。別の関数では、Bをロックし始め、しばらくしてからAをロックします。 2番目の関数がAのロック解除を待機するときに最初の関数がBのロック解除を待機する潜在的なデッドロックです。この問題はおそらく開発環境では発生せず、時々発生するだけです。これを回避するには、すべてのロックを常に同じ順序にする必要があります。
はいといいえ。
スレッドセーフティは、共有データに一度に1つのスレッドのみがアクセスすることを確認するだけではありません。共有データへのシーケンシャルアクセスを確保すると同時に、競合状態、デッドロック、ライブロック、リソース不足を回避する必要があります。ます。
複数のスレッドが実行されている場合の予期しない結果は、スレッドセーフコードの必須条件ではありませんが、多くの場合副産物です。たとえば、共有キュー、1つのプロデューサースレッド、およびいくつかのコンシューマースレッドを使用してプロデューサー/コンシューマースキームを設定すると、データフローは完全に予測可能になる場合があります。より多くの消費者を紹介し始めると、よりランダムに見える結果が表示されます。
本質的に、マルチスレッド環境では多くのことがうまくいかない可能性があります(命令の並べ替え、部分的に構築されたオブジェクト、CPUレベルでのキャッシュのために異なるスレッドで同じ値を持つ同じ変数など)。
私はJava Concurrency in Practiceで与えられた定義が好きです:
[コードの一部]は、ランタイム環境によるそれらのスレッドの実行のスケジューリングやインターリーブに関係なく、複数のスレッドからアクセスされたときに正しく動作し、追加の同期やその他の調整がない場合、スレッドセーフです。呼び出しコード。
することで、正しく彼らはプログラムがその仕様に準拠した動作することを意味します。
考案された例
カウンターを実装するとします。次の場合、正しく動作すると言えます。
counter.next()
以前にすでに返された値を決して返さない(簡単にするために、オーバーフローなどは想定していない)スレッドセーフカウンターは、同時にアクセスするスレッドの数に関係なく、これらのルールに従って動作します(通常、単純な実装の場合はそうではありません)。
単に-多くのスレッドがこのコードを同時に実行している場合、コードは正常に実行されます。
スレッドの安全性と確定性を混同しないでください。スレッドセーフコードは、非決定的である場合もあります。スレッド化されたコードでの問題のデバッグが困難であることを考えると、これはおそらく通常のケースです。:-)
スレッドセーフティは、スレッドが共有データを変更または読み取るときに、他のスレッドがデータを変更する方法でそれにアクセスできないようにするだけです。コードが正確さのために実行の特定の順序に依存している場合、これを確実にするには、スレッドセーフティに必要なもの以外の同期メカニズムが必要です。
他の良い答えの上にいくつかの情報を追加したいと思います。
スレッドセーフとは、複数のスレッドがメモリの不整合エラーなしに同じオブジェクトでデータを読み書きできることを意味します。高度にマルチスレッド化されたプログラムでは、スレッドセーフプログラムは共有データに副作用を引き起こしません。
詳細については、このSEの質問をご覧ください。
スレッドセーフプログラムは、メモリの一貫性を保証します。
高度なコンカレントAPIに関するOracleのドキュメントページから:
メモリの一貫性のプロパティ:
Java™言語仕様の第17章では、共有変数の読み取りや書き込みなどのメモリ操作に関するハプネスビフォアリレーションを定義しています。1つのスレッドによる書き込みの結果は、読み取り操作の前に書き込み操作が発生した場合にのみ、別のスレッドによる読み取りで認識されることが保証されます。。
synchronized
及びvolatile
構築物だけでなく、Thread.start()
およびThread.join()
方法、缶フォームは事前発生します関係。
java.util.concurrent
とそのサブパッケージのすべてのクラスのメソッドは、これらの保証をより高いレベルの同期に拡張します。特に:
Runnable
にExecutor
起こる-前にその実行が開始されます。同様に、に送信された呼び出し可能オブジェクトExecutorService
。Future
結果を取得した後の前に発生するアクションによって表される非同期計算によって実行されるアクションFuture.get()
。Lock.unlock, Semaphore.release, and CountDownLatch.countDown
前のアクションLock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
。Exchanger
に、exchange()
、各スレッドでのは、別のスレッドでの対応するexchange()の後の発生します。CyclicBarrier.await
およびPhaser.awaitAdvance
(およびそのバリアントと同様に)バリアアクションによって実行されるビフォアビフォアアクション、およびバリアアクションによって実行されるアクションは、他のスレッドでの対応する待機からの戻りに成功した後のビフォアビフォアアクションです。他の回答を完了するには:
メソッドのコードが次の2つのうちの1つを実行する場合にのみ、同期が問題になります。
つまり、メソッド内で定義された変数は常にスレッドセーフです。メソッドへのすべての呼び出しには、これらの変数の独自のバージョンがあります。メソッドが別のスレッドまたは同じスレッドによって呼び出された場合、またはメソッドがそれ自体を呼び出した場合(再帰)でも、これらの変数の値は共有されません。
スレッドのスケジューリングは、ラウンドロビンであることが保証されていません。タスクは、同じ優先度のスレッドを犠牲にして、CPUを完全に占有する場合があります。Thread.yield()を使用して良心を持つことができます。(Javaで)Thread.setPriority(Thread.NORM_PRIORITY-1)を使用してスレッドの優先順位を下げることができます
さらに、次のことに注意してください。
例でこれに答えましょう:
class NonThreadSafe {
private int counter = 0;
public boolean countTo10() {
count = count + 1;
return (count == 10);
}
このcountTo10
メソッドは、カウンターに1を追加し、カウントが10に達した場合にtrueを返します。trueは1回だけ返す必要があります。
これは、1つのスレッドのみがコードを実行している限り機能します。2つのスレッドが同時にコードを実行すると、さまざまな問題が発生する可能性があります。
たとえば、countが9で始まる場合、1つのスレッドがcountに1を追加(10にする)できますが、最初のスレッドが10との比較を実行する前に、2番目のスレッドがメソッドに入り、もう一度1を追加(11にする)できます。その後、両方のスレッドが比較を行い、countが11であり、どちらもtrueを返さないことがわかります。
したがって、このコードはスレッドセーフではありません。
本質的に、すべてのマルチスレッドの問題は、この種の問題のバリエーションによって引き起こされます。
解決策は、加算と比較を分離できないようにすることです(たとえば、2つのステートメントをある種の同期コードで囲むことによって)、または2つの操作を必要としない解決策を考案することによって。このようなコードはスレッドセーフです。
少なくともC ++では、スレッドセーフは、名前から多くの部分が除外されているという点で、少し誤った名称であると考えています。スレッドセーフになるためには、コードは通常、プロアクティブでなければなりません。それは一般的に受動的な品質ではありません。
クラスをトレッドセーフにするには、オーバーヘッドを追加する「追加」機能が必要です。これらの機能は、クラスの実装の一部であり、一般的に言えば、インターフェースから隠されています。つまり、別のスレッドは、別のスレッドによる同時アクセスと競合することを心配する必要なく、クラスのメンバーのいずれかにアクセスできます。また、実行する必要なく、非常に遅延した方法で、プレーンな古い通常の人間のコーディングスタイルを使用できます。呼び出されているコードの根幹にすでに組み込まれているすべてのクレイジーな同期のこと。
そして、これが一部の人々が内部的に同期されたという用語を使うことを好む理由です。
私が遭遇したこれらのアイデアには、3つの主要な用語セットがあります。最初の、そして歴史的にもっと人気のある(しかし悪い)は:
2番目(およびそれ以上)は次のとおりです。
3番目は次のとおりです。
スレッドセーフ〜スレッドプルーフ〜内部同期
例内部で同期(別名。スレッドセーフまたはスレッドの証明)システムは、ホストがドアであなたを迎えて、そして自分自身をキューイングから禁止しますがレストランです。ホストは、複数の顧客に対応するためのレストランのメカニズムの一部であり、パーティーの規模や彼らが持っているように見える時間を考慮に入れるなど、待っている顧客の座席を最適化するためにいくつかのトリッキーなトリックを使用できます、または電話でのご予約も承っております。これはすべて、レストランと対話するためのインターフェースの一部であるため、レストランは内部で同期されています。
スレッドセーフではありません(ただし便利です)〜スレッド互換性があります〜外部同期されます〜フリースレッド
銀行に行くとしましょう。行、すなわち銀行の出納係の競合があります。あなたは野蛮人ではないので、リソースの競合の最中に行うには、文明化された存在のように列を作ることが最善であると認識しています。技術的にこれを行う人は誰もいません。私たちはあなたが自分でそれを行うために必要なソーシャルプログラミングを持っていると思います。この意味で、銀行のロビーは外部で同期されています。スレッドセーフではないと言えるでしょうか。これが、スレッドセーフ、スレッドアンセーフのバイポーラ用語セットを使用する場合の意味です。それは非常に良い用語のセットではありません。より良い用語は外部的に同期され、銀行のロビーは複数の顧客からのアクセスに敵対的ではありませんが、顧客を同期する作業も行いません。顧客自身がそれを行います。
これは「フリースレッド」とも呼ばれ、「フリー」は「シラミから解放」の場合と同じです。この場合、ロックです。より正確には、同期プリミティブ。これは、コードがそれらのプリミティブなしで複数のスレッドで実行できることを意味しません。それはそれらが既にインストールされていないことを意味するだけであり、あなたが自分でインストールするかどうかは、コードのユーザーであるあなた次第です。独自の同期プリミティブをインストールすることは難しい場合があり、コードをよく考える必要がありますが、今日のハイパースレッドCPUでのプログラムの実行方法をカスタマイズできるため、プログラムを可能な限り最速にすることもできます。
スレッドセーフではない(そして悪い)〜スレッドに敵対的〜非同期
スレッド敵対的なシステムの日常の例えは、スポーツカーがウインカーの使用を拒否し、車線を自由に変更することです。それらの運転スタイルは、それらと調整する方法がないため、スレッドに敵対的または非同期です。これにより、解決策なしに同じ車線の競合が発生し、2台の車がプロトコルを使用せずに同じスペースを占有しようとする事故が発生する可能性があります。これを防ぐ。このパターンは、より広く反社会的であると考えることもできます。これは、スレッドに固有ではなく、プログラミングの多くの領域に一般的に適用できるためです。
最初と最も古い用語セットでは、スレッドの敵意とスレッドの互換性をより細かく区別できません。スレッドの互換性は、いわゆるスレッドセーフティよりパッシブですが、呼び出されたコードがスレッドの同時使用に対して安全ではないという意味ではありません。これは、内部の実装の一部として提供するのではなく、これを可能にする同期について受動的であることを意味し、呼び出し元のコードにそれを延期します。スレッド互換性は、ほとんどの場合、デフォルトでコードがおそらく記述される方法ですが、悲しいことに、これは、本質的にアンチセーフティであるかのように、スレッドセーフではないと誤って考えられることが多く、プログラマにとって混乱の主なポイントです。
注:多くのソフトウェアマニュアルでは、「スレッドセーフ」という用語を実際に使用して「スレッド互換」を表しているため、すでに混乱していたものにさらに混乱が生じています。この理由のために、「スレッドセーフ」と「スレッドアンセーフ」という用語は絶対に避けます。同意できないために「スレッドセーフ」と呼ぶソースもあれば、「スレッドアンセーフ」と呼ぶソースもあるからです。安全性に関するいくつかの追加基準(同期プリミティブ)を満たさなければならないのか、それとも単に「安全」と見なされるために敵対的であってはならないのかについて。したがって、これらの用語を避け、代わりにより賢明な用語を使用して、他のエンジニアとの危険な誤解を回避してください。
基本的に、私たちの目標はカオスを破壊することです。
私たちは、信頼できる確定的システムを作成することでそれを実現します。決定論は高価であり、主に並列性の喪失、パイプライン処理、および並べ替えの機会費用が原因です。私たちは、コストを低く抑えるために必要な決定論の量を最小限に抑える一方で、私たちが提供できる小さな決定論をさらに浸食するような決定をすることを避けます。
スレッドの同期とは、順序を増やしてカオスを減らすことです。これを行うレベルは、上記の条件に対応しています。最高レベルとは、システムが毎回完全に予測可能な方法で動作することを意味します。2番目のレベルは、システムが十分に動作して、呼び出しコードが予測不能性を確実に検出できることを意味します。たとえば、条件変数での誤ったウェイクアップや、準備ができていないためのミューテックスのロックの失敗などです。3番目のレベルは、システムが他の誰かと遊ぶのに十分な動作をせず、混乱を招かずにシングルスレッドでのみ実行できることを意味します。
代わりの思考のコードやクラススレッドセーフかどうかなど、私はそれを考えることがより有用であると考え行動スレッドセーフであることなど。2つのアクションは、任意のスレッドコンテキストから実行されたときに指定どおりに動作する場合、スレッドセーフです。多くの場合、クラスはアクションのいくつかの組み合わせをスレッドセーフな方法でサポートし、他の組み合わせはサポートしません。
たとえば、配列リストやハッシュセットなどの多くのコレクションは、最初に1つのスレッドで排他的にアクセスされ、他のスレッドから参照が見えるようになった後は変更されない場合、任意の組み合わせで任意の方法で読み取られることを保証します干渉のない糸の。
さらに興味深いことに、.NETの元の非ジェネリックコレクションなどの一部のハッシュセットコレクションは、アイテムが削除されない限り、1つのスレッドのみがそれらに書き込みを行うという保証を提供します。コレクションを読み取ると、更新が遅延して任意の順序で発生する可能性があるコレクションにアクセスするかのように動作しますが、それ以外の場合は正常に動作します。スレッド#1がXを追加してからYを追加し、スレッド#2がYを検索してからXを検索する場合、スレッド#2はYは存在するがXは存在しないことを確認できます。そのような動作が「スレッドセーフ」であるかどうかは、スレッド#2がその可能性に対処する準備ができているかどうかに依存します。
最後の注記として、一部のクラス(特に通信ライブラリのブロック)には、他のすべてのメソッドに対してスレッドセーフである「close」または「Dispose」メソッドがありますが、スレッドセーフである他のメソッドはありません。お互い。スレッドがブロック読み取り要求を実行し、プログラムのユーザーが[キャンセル]をクリックした場合、読み取りを実行しようとしているスレッドが閉じる要求を発行する方法はありません。ただし、クローズ/破棄要求は非同期でフラグを設定する可能性があり、これにより読み取り要求ができるだけ早く取り消されます。いずれかのスレッドでcloseが実行されると、オブジェクトは役に立たなくなり、以降のアクションでのすべての試行はすぐに失敗します。