web-dev-qa-db-ja.com

「スレッドセーフ」コードとはどういう意味ですか?

2つのスレッドが基になるデータを同時に変更できないということですか?または、複数のスレッドが実行されている場合、特定のコードセグメントが予測可能な結果で実行されることを意味しますか?

327
Varun Mahajan

ウィキペディアから:

スレッドセーフは、マルチスレッドプログラムのコンテキストで適用可能なコンピュータープログラミングの概念です。複数のスレッドによる同時実行中に正しく機能する場合、コードはスレッドセーフです。特に、複数のスレッドが同じ共有データにアクセスする必要性と、データの共有部分に常に1つのスレッドのみがアクセスする必要性を満たす必要があります。

スレッドセーフを実現する方法はいくつかあります。

再入可能性:

1つのタスクによって部分的に実行され、別のタスクによって再入力され、元のタスクから再開できるような方法でコードを記述します。これには、静的またはグローバル変数ではなく、通常はスタック上の各タスクにローカルな変数に状態情報を保存する必要があります。

相互排除:

共有データへのアクセスは、常に1つのスレッドのみが共有データの読み取りまたは書き込みを行うメカニズムを使用してシリアル化されます。コードが複数の共有データにアクセスする場合、細心の注意が必要です。問題には、競合状態、デッドロック、ライブロック、飢v、および多くのオペレーティングシステムの教科書に列挙されている他のさまざまな病気が含まれます。

スレッドローカルストレージ:

変数はローカライズされているため、各スレッドには独自のプライベートコピーがあります。これらの変数は、サブルーチンや他のコードの境界を越えて値を保持し、それらにアクセスするコードがリエントラントであっても、各スレッドに対してローカルであるため、スレッドセーフです。

原子操作:

共有データは、他のスレッドによって中断されないアトミック操作を使用してアクセスされます。これには通常、ランタイムライブラリで使用可能な特別な機械語命令を使用する必要があります。操作はアトミックであるため、共有データは、他のスレッドがどのデータにアクセスしても、常に有効な状態に維持されます。アトミック操作は、多くのスレッドロックメカニズムの基礎を形成します。

続きを読む:

http://en.wikipedia.org/wiki/Thread_safety


242
Blauohr

スレッドセーフコードは、多くのスレッドが同時に実行している場合でも機能するコードです。

http://mindprod.com/jgloss/threadsafe.html

73
Marek Blotny

より有益な質問は、コードをスレッドセーフにしない理由です。答えは、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
  1. 最初の条件は、複数のスレッドからアクセス可能なメモリ位置があることです。通常、これらの場所はグローバル/静的変数であるか、グローバル/静的変数から到達可能なヒープメモリです。各スレッドは、関数/メソッドスコープのローカル変数に対して独自のスタックフレームを取得するため、これらのローカル関数/メソッド変数otoh(スタック上にある)は、そのスタックを所有する1つのスレッドからのみアクセスできます。
  2. 2番目の条件は、これらの共有メモリの場所に関連付けられているプロパティ(invariantと呼ばれる)が存在することです。 、プログラムが正しく機能するため。上記の例では、プロパティは「totalRequestsは、スレッドがインクリメントステートメントの一部を実行した合計回数を正確に表す必要があります」です。通常、更新が正しく行われるように更新が行われる前に、この不変プロパティはtrueを保持する必要があります(この場合、totalRequestsは正確なカウントを保持する必要があります)。
  3. 3番目の条件は、実際の更新の一部で不変プロパティが保持されないことです。 (処理の一部で一時的に無効または偽です)。この特定のケースでは、totalRequestsがフェッチされてから更新された値が格納されるまで、totalRequestsは不変式をnot満たします。
  4. レースが発生するために発生する必要がある4番目の最終条件(およびコードのためNOT be "スレッドセーフ」とは、別のスレッドが共有メモリにアクセスできる必要があることですwhile不変式が壊れているため、一貫性のない、または誤った動作を引き起こします。
47
Charles Bretana

ブライアンゲッツのJava同時実行性の定義の包括性が気に入っています

「ランタイム環境によるスレッドの実行のスケジューリングまたはインターリーブに関係なく、複数のスレッドからアクセスされたときに正しく動作し、呼び出しコード側で追加の同期または他の調整なしで、クラスはスレッドセーフです。 」

32
Buu Nguyen

他の人が指摘しているように、スレッドセーフとは、コードが一度に複数のスレッドで使用された場合にエラーなしで動作することを意味します。

これには、コンピューターの時間とより複雑なコーディングのコストがかかる場合があるため、常に望ましいとは限らないことに注意してください。クラスを1つのスレッドでのみ安全に使用できる場合は、その方が適切な場合があります。

たとえば、Javaには、ほぼ同等の2つのクラスStringBufferStringBuilderがあります。違いは、StringBufferはスレッドセーフであるため、StringBufferの単一のインスタンスを複数のスレッドで同時に使用できることです。 StringBuilderはスレッドセーフではなく、Stringが1つのスレッドのみで構築されている場合(大多数)の高性能な代替として設計されています。

26
Marcus Downing

スレッドセーフコードは、異なるスレッドから同時に入力された場合でも、指定どおりに機能します。これは、多くの場合、中断することなく実行される内部データ構造または操作が、異なる変更から同時に保護されることを意味します。

22
Mnementh

それを理解する簡単な方法は、コードをスレッドセーフではないものにすることです。スレッド化されたアプリケーションに望ましくない動作をさせる主な問題が2つあります。

  • ロックせずにシェア変数にアクセスする
    この変数は、関数の実行中に別のスレッドによって変更される可能性があります。あなたはあなたの関数の振る舞いを確実にするためにロック機構でそれを防ぎたいです。一般的な経験則は、可能な限り短時間ロックを保持することです。

  • シェア変数への相互依存関係によるデッドロック
    2つの共有変数AとBがある場合。1つの関数で最初にAをロックし、その後Bをロックします。別の関数でBのロックを開始し、しばらくするとAをロックします。デッドロック。最初の関数はBのロック解除を待機し、2番目の関数はAのロック解除を待機します。この問題はおそらく開発環境では発生せず、時々のみ発生します。これを回避するには、すべてのロックが常に同じ順序でなければなりません。

20
Hapkido

はいといいえ。

スレッドセーフは、一度に1つのスレッドのみが共有データにアクセスすることを確認するだけではありません。共有データへのシーケンシャルアクセスを保証すると同時に、 競合状態デッドロックライブロック 、および リソース飢v

複数のスレッドが実行されている場合の予測不能な結果は、notスレッドセーフコードの必須条件ですが、多くの場合、副産物です。たとえば、共有キュー、1つのプロデューサスレッド、および少数のコンシューマスレッドでセットアップされた producer-consumer スキームを使用でき、データフローは完全に予測可能です。より多くの消費者を紹介し始めると、よりランダムな結果が表示されます。

9
Bill the Lizard

本質的に、マルチスレッド環境では多くのことが間違っている可能性があります(命令の並べ替え、部分的に構築されたオブジェクト、CPUレベルでのキャッシュのために異なるスレッドで異なる変数を持つ同じ変数など)。

Java並行性の実践 で与えられる定義が好きです:

[コードの部分]は、ランタイム環境によるそれらのスレッドの実行のスケジューリングまたはインターリーブに関係なく、複数のスレッドからアクセスされたときに正しく動作し、追加の同期またはその他の調整なしでスレッドセーフです。呼び出しコード。

正しくとは、プログラムが仕様に準拠して動作することを意味します。

考案された例

カウンターを実装すると想像してください。次の場合に正しく動作すると言うことができます。

  • counter.next()は、以前に既に返された値を決して返しません(簡単にするためにオーバーフローなどがないと仮定します)
  • 0から現在の値までのすべての値が何らかの段階で返されました(値はスキップされません)

スレッドセーフカウンターは、同時にアクセスするスレッドの数に関係なく、これらのルールに従って動作します(通常、単純な実装の場合はそうではありません)。

注: プログラマーのクロスポスト

8
assylias

単純に-多くのスレッドがこのコードを同時に実行している場合、コードは正常に実行されます。

7
Greg Balajewicz

他の良い答えの上にさらに情報を追加したいと思います。

スレッドの安全性は、複数のスレッドがメモリの不整合エラーなしで同じオブジェクトのデータを読み書きできることを意味します。高度にマルチスレッド化されたプログラムでは、スレッドセーフプログラムは共有データに副作用を引き起こしません

詳細については、このSEの質問をご覧ください。

スレッドセーフとはどういう意味ですか?

スレッドセーフプログラムはメモリの一貫性を保証します

Oracleのドキュメントから page 高度な並行APIについて:

メモリ整合性プロパティ:

Java™言語仕様の第17章では、共有変数の読み取りや書き込みなどのメモリ操作に関する発生前関係を定義しています。 1つのスレッドによる書き込みの結果は、書き込み操作が読み取り操作の前に発生した場合にのみ、別のスレッドによる読み取りから見えることが保証されます

synchronizedおよびvolatileコンストラクト、およびThread.start()およびThread.join()メソッドは、happens-before関係を形成できます。

Java.util.concurrentおよびそのサブパッケージのすべてのクラスのメソッドは、これらの保証をより高いレベルの同期に拡張します。特に:

  1. オブジェクトを同時コレクションに配置する前のスレッド内のアクションは、別のスレッド内のコレクションへのその要素のアクセスまたは削除に続くアクションの前に発生します。
  2. RunnableExecutorを送信する前のスレッド内のアクションは、実行が開始される前に発生します。同様に、ExecutorServiceに送信された呼び出し可能オブジェクトの場合。
  3. 別のスレッドでFuture.get()を介して結果を取得した後のFutureで表される非同期計算によって実行されるアクションは、発生前アクションです。
  4. 別のスレッドの同じシンクロナイザーオブジェクトでLock.unlock, Semaphore.release, and CountDownLatch.countDownなどの成功した「取得」メソッドに続くLock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.awaitなどの「リリース」synchronizerメソッドの前のアクションが発生する前のアクション。
  5. Exchangerを介してオブジェクトを正常に交換するスレッドのペアごとに、各スレッドのexchange()の前のアクションが、別のスレッドの対応するexchange()の後続アクションの前に発生します。
  6. CyclicBarrier.awaitおよびPhaser.awaitAdvance(およびそのバリアント)を呼び出す前のアクションは、バリアアクションによって実行される前に発生します。
5
Ravindra babu

スレッドセーフと確定性を混同しないでください。スレッドセーフコードも非決定的です。スレッド化されたコードで問題をデバッグするのが難しいことを考えると、これはおそらく通常のケースです。 :-)

スレッドセーフは、スレッドが共有データを変更または読み取るときに、他のスレッドがデータを変更する方法でアクセスできないようにすることを保証するだけです。コードが実行の正確性のために特定の順序に依存している場合、これを確実にするためにスレッドセーフに必要なもの以外の同期メカニズムが必要です。

4
tvanfosson

他の回答を完了するには:

同期は、メソッドのコードが次の2つのいずれかを実行する場合にのみ心配です。

  1. スレッドセーフではない外部リソースで動作します。
  2. 永続オブジェクトまたはクラスフィールドの読み取りまたは変更

つまり、メソッド内で定義された変数は常にスレッドセーフです。メソッドを呼び出すたびに、これらの変数の独自のバージョンがあります。メソッドが別のスレッドまたは同じスレッドによって呼び出された場合、またはメソッドがそれ自体を呼び出した場合でも(再帰)、これらの変数の値は共有されません。

スレッドのスケジューリングは、ラウンドロビンであるとは限りません。タスクは、同じ優先度のスレッドを犠牲にしてCPUを完全に占有する場合があります。 Thread.yield()を使用して良心を持つことができます。 (Javaで)Thread.setPriority(Thread.NORM_PRIORITY-1)を使用して、スレッドの優先度を下げることができます。

さらに注意してください:

  • これらの「スレッドセーフ」構造を反復処理するアプリケーションの実行時コストが大きい(すでに他の人が言及しています).
  • Thread.sleep(5000)は5秒間スリープすることになっています。ただし、誰かがシステムの時刻を変更した場合、非常に長い時間スリープするか、まったくスリープしないことがあります。 OSは、ウェイクアップ時間を相対形式ではなく絶対形式で記録します。
3
VonC

はい、はい。これは、データが複数のスレッドによって同時に変更されないことを意味します。ただし、プログラムは基本的に動作しない場合でも、期待どおりに動作し、スレッドセーフに見える場合があります。

結果の予測不能性は、おそらく予想される順序以外の順序でデータが変更される「競合条件」の結果であることに注意してください。

1
Steve Knight

例でこれに答えましょう。

class NonThreadSafe {

    private int counter = 0;

    public boolean countTo10() {
        count = count + 1;
        return (count == 10);
    }

countTo10メソッドは、カウンターに1を追加し、カウントが10に達した場合にtrueを返します。trueを1回だけ返す必要があります。

これは、1つのスレッドのみがコードを実行している限り機能します。 2つのスレッドが同時にコードを実行すると、さまざまな問題が発生する可能性があります。

たとえば、カウントが9から始まる場合、1つのスレッドはカウントに1を追加し(10を作成)、2番目のスレッドはメソッドに入り、最初のスレッドが10との比較を実行する前に再度1を追加します(11を作成)次に、両方のスレッドが比較を行い、カウントが11で、どちらもtrueを返さないことを確認します。

したがって、このコードはスレッドセーフではありません。

本質的に、すべてのマルチスレッドの問題は、この種の問題のいくつかのバリエーションによって引き起こされます。

解決策は、追加と比較を分離できないようにすることです(たとえば、2つのステートメントを何らかの同期コードで囲むことによって)か、2つの操作を必要としないソリューションを考案します。このようなコードはスレッドセーフです。

0
rghome

最も簡単な言葉で:Pコードのブロックで複数のスレッドを実行しても安全であれば、スレッドセーフです*

*条件が適用されます

条件は、1などの他の回答で言及されています。1つのスレッドまたは複数のスレッドを実行した場合など、結果は同じになります。

0
shabby