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


123

最近、UIスレッド以外のスレッドからテキストボックスにアクセスしようとすると、例外がスローされました。それは「コードがスレッドセーフではない」ことについて何かを言っていたので、結局、デリゲート(MSDNのサンプルを参考にした)を書き、代わりにそれを呼び出しました。

しかし、それでも、なぜすべての追加コードが必要なのかはよくわかりませんでした。

更新:チェックすると深刻な問題が発生しますか

Controls.CheckForIllegalCrossThread..blah =true

5
通常、「スレッドセーフ」とは、その用語を使用している人が、その人がそれを意味すると考えているものは何でも、少なくともその人にとっては意味します。そのため、これはあまり有用な言語構成ではありません。スレッド化されたコードの動作について話すときは、はるかに具体的にする必要があります。


@dave申し訳ありませんが、私が探してみましたが、あきらめた...おかげでとにかく...
のVivekバーナード

1
発生しないコードRace-Condition
Muhammad Babar 2014年

回答:


121

Eric Lippertは、「スレッドセーフ」と呼ぶこのタイトルのいいブログ投稿を持っています。ウィキペディアのスレッドセーフの定義について。

リンクから抽出された3つの重要な事項:

「複数のスレッドによる同時実行中に正しく機能すれば、コードの一部はスレッドセーフです。」

「特に、複数のスレッドが同じ共有データにアクセスする必要性を満たす必要があります…」

「…そして、ある時点で1つのスレッドだけが共有データにアクセスする必要性。」

間違いなく読む価値があります!


24
解答のみのリンクは、将来いつでも悪くなる可能性があるので避けてください。
akhil_mittal 2015


106

簡単に言うと、スレッドセーフとは、複数のスレッドからアクセスしても安全であることを意味します。プログラムで複数のスレッドを使用していて、それぞれがメモリ内の共通のデータ構造または場所にアクセスしようとすると、いくつかの問題が発生する可能性があります。したがって、これらの悪いことを防ぐためにいくつかの追加コードを追加します。たとえば、2人が同じドキュメントを同時に書き込んでいた場合、2番目に保存した人が最初の人の作業を上書きします。スレッドセーフにするためには、ユーザー2にドキュメントの編集を許可する前に、ユーザー2にユーザー1のタスクが完了するのを待機させる必要があります。


11
これは同期と呼ばれます。正しい?
JavaTechnical 2014

3
はい。さまざまなスレッドに強制的に共有リソースへのアクセスを待機させるには、同期を使用します。
Vincent Ramdhanie、2014

グレゴリーの認められた答えから、彼は「複数のスレッドによる同時実行中に正しく機能する場合、コードの一部はスレッドセーフです」と述べています。「スレッドセーフにするためには、1人目を強制的に待たなければならない」と言っているとき、同時ではないと言っているのではないですか?それとも説明できますか?
Honey

同じことです。私はコードをスレッドセーフにするものの例として単純なメカニズムを提案しているだけです。同じコードを実行している複数のスレッドが互いに干渉することはありませんが、使用されるメカニズムに関係なく。
Vincent Ramdhanie

それでは、これはグローバル変数と静的変数を利用するコードにのみ適用されますか?文書を編集している人々の例を使用すると、2人が別の文書で文書作成コードを実行するのを防ぐのは意味がないと思います。
アーロンフランケ2017年

18

ウィキペディアには、スレッドセーフに関する記事があります。

この定義ページ(広告をスキップする必要があります-申し訳ありません)は次のように定義します。

コンピュータプログラミングでは、スレッドセーフとは、スレッド間の不要な相互作用なしに複数のプログラミングスレッドから呼び出すことができるプログラム部分またはルーチンを指します。

スレッドはプログラムの実行パスです。シングルスレッドプログラムは1つのスレッドしか持たないため、この問題は発生しません。事実上すべてのGUIプログラムには複数の実行パスがあり、したがってスレッドがあります。少なくとも2つあり、1つはGUIの表示を処理してユーザー入力を処理するためのもので、もう1つは実際にプログラムの操作を実行するためのものです。

これは、長時間実行されているプロセスを非UIスレッドにオフロードすることにより、プログラムが動作している間もUIが応答するようにするためです。これらのスレッドは一度作成され、プログラムの存続期間中存在するか、必要なときに作成され、終了時に破棄されます。

これらのスレッドは、一般的なアクション(ディスクI / O、結果の画面への出力など)を実行する必要があることが多いため、コードのこれらの部分は、複数のスレッドから呼び出されることを処理できるように記述する必要があります。同時に。これには次のようなものが含まれます。

  • データのコピーでの作業
  • 重要なコードの周りにロックを追加する

8

簡単に言うと、スレッドセーフとは、メソッドまたはクラスインスタンスを問題なく複数のスレッドで同時に使用できることを意味します。

次の方法を検討してください。

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

ここで、スレッドAとスレッドBの両方がAddOne()を実行しようとしています。Aが最初に開始し、myInt(0)の値をtmpに読み取ります。ここで、何らかの理由で、スケジューラはスレッドAを停止し、実行をスレッドBに延期することを決定します。スレッドBは、myInt(まだ0)の値を独自の変数tmpに読み取ります。スレッドBはメソッド全体を終了するため、最終的にmyInt = 1となり、1が返されます。今度はスレッドAの番です。スレッドAは続行します。そして、tmpに1を追加します(スレッドAの場合、tmpは0でした)。そして、この値をmyIntに保存します。myIntは再び1です。

したがって、この場合、メソッドAddOneが2回呼び出されましたが、メソッドがスレッドセーフな方法で実装されていなかったため、myIntの値は期待どおり2ではなく、2番目のスレッドが最初のスレッドが完了する前に変数myIntを読み取ったので1更新しています。

スレッドセーフメソッドを作成することは、重要なケースでは非常に困難です。そして、かなりの数のテクニックがあります。Javaでは、メソッドに同期済みのマークを付けることができます。つまり、一度に1つのスレッドだけがそのメソッドを実行できます。他のスレッドは並んで待機します。これにより、メソッドはスレッドセーフになりますが、メソッドで実行する作業が多い場合は、多くのスペースを無駄にします。別の手法は、「メソッドの一部のみを同期済みとしてマークする」ことですロックまたはセマフォを作成し、この小さな部分をロックします(通常、クリティカルセクションと呼ばれます)。ロックレススレッドセーフとして実装されているメソッドもいくつかあります。つまり、複数のスレッドが問題を発生させることなく同時にそれらを介して競合できるように構築されています。これは、メソッドのみの場合です。 1つのアトミックコールを実行します。アトミックコールは、中断することができず、一度に1つのスレッドしか実行できないコールです。


AddOneメソッドが2回呼び出された場合
Sujith PS '19 / 09/19

6

素人の実例は

あなたがインターネットとモバイルバンキングのある銀行口座を持っていて、あなたの口座がたった10ドルしかないとしましょう。モバイルバンキングを使用して別の口座に残高を移動し、その間、同じ銀行口座を使用してオンラインショッピングを行いました。この銀行口座がスレッドセーフでない場合、銀行は2つのトランザクションを同時に実行することを許可し、銀行は破産します。

スレッドセーフとは、複数のスレッドが同時にオブジェクトにアクセスしようとしても、オブジェクトの状態が変化しないことを意味します。


5

あなたは本「Java Concurrency in Practice」からより多くの説明を得ることができます:

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


4

モジュールは、マルチスレッドおよび同時使用に直面してもその不変条件を維持できることが保証されている場合、スレッドセーフです。

ここで、モジュールは、データ構造、クラス、オブジェクト、メソッド/プロシージャ、または関数です。基本的にスコープされたコードと関連データ。

保証は、特定のCPUアーキテクチャなどの特定の環境に限定される可能性がありますが、それらの環境に対応する必要があります。環境の明示的な区切りがない場合、通常は、コードをコンパイルして実行できるすべての環境に当てはまることを意味すると見なされます。

スレッドセーフでないモジュール、マルチスレッドおよび同時使用で正しく機能する可能性があります、慎重に設計するよりも、運と偶然の一致が原因であることがよくあります。一部のモジュールが正常に機能しない場合でも、他の環境に移動するとモジュールが破損する可能性があります。

マルチスレッドのバグは、多くの場合デバッグが困難です。それらのいくつかはたまにしか発生しませんが、他のものは積極的に現れます-これも環境固有である可能性があります。それらは、微妙に間違った結果、またはデッドロックとして現れる可能性があります。それらは、予期しない方法でデータ構造を混乱させ、コードの他のリモート部分に他の表面上は不可能と思われるバグを発生させる可能性があります。これは非常にアプリケーション固有なので、一般的な説明をするのは困難です。


3

スレッドセーフ:スレッドセーフプログラムは、データをメモリの一貫性エラーから保護します。高度にマルチスレッド化されたプログラムでは、スレッドセーフプログラムは、同じオブジェクトの複数のスレッドからの複数の読み取り/書き込み操作による副作用を引き起こしません。異なるスレッドが一貫性​​エラーなしでオブジェクトデータを共有および変更できます。

高度な同時実行APIを使用してスレッドセーフを実現できます。このドキュメントページは、スレッドセーフを実現するための優れたプログラミング構造を提供します。

ロックオブジェクトは、多くの同時アプリケーションを簡素化するロックイディオムをサポートします。

エグゼキュータは、スレッドを起動および管理するための高レベルAPIを定義します。java.util.concurrentによって提供されるエグゼキューター実装は、大規模なアプリケーションに適したスレッドプール管理を提供します。

並行コレクションを使用すると、大量のデータのコレクションを管理しやすくなり、同期の必要性を大幅に減らすことができます。

原子変数には、同期を最小限に抑え、メモリの一貫性エラーを回避するのに役立つ機能があります。

ThreadLocalRandom(JDK 7の場合)は、複数のスレッドから擬似乱数を効率的に生成します。

他のプログラミング構成については、java.util.concurrentおよびjava.util.concurrent.atomicパッケージも参照してください。


1

あなたは明らかにWinForms環境で働いています。WinFormsコントロールはスレッドアフィニティを示します。つまり、コントロールが作成されたスレッドは、それらにアクセスして更新するために使用できる唯一のスレッドです。そのため、MSDNやその他の場所で、コールをメインスレッドにマーシャリングする方法を示す例が見つかります。

WinFormsの通常のプラクティスは、すべてのUI作業に専用の単一のスレッドを持つことです。


1

私は、http://en.wikipedia.org/wiki/Reentrancy_%28computing%29の概念が、メソッドがグローバル変数などの副作用に依存している場合の、安全でないスレッド化と通常考えるものだと思います。

たとえば、浮動小数点数を文字列にフォーマットしたコードを見ましたが、これらの2つが異なるスレッドで実行される場合、decimalSeparatorのグローバル値は永久に「。」に変更できます。

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp

-2

スレッドの安全性を理解するには、以下のセクションをお読みください。

4.3.1。例:委任を使用した車両追跡

委任のより重要な例として、スレッドセーフなクラスに委任する車両追跡のバージョンを作成してみましょう。場所はMapに保存するため、スレッドセーフなMap実装から始めConcurrentHashMapます。MutablePointリスト4.6に示すように、の代わりに不変のPointクラスを使用して場所を保存します。

リスト4.6 DelegatingVehicleTrackerによって使用される不変のPointクラス。

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

Point不変であるため、スレッドセーフです。不変の値は自由に共有および公開できるため、返すときに場所をコピーする必要はありません。

DelegatingVehicleTrackerリスト4.7では、明示的な同期は使用していません。状態へのすべてのアクセスはによって管理されConcurrentHashMap、マップのすべてのキーと値は不変です。

リスト4.7 スレッドセーフティをConcurrentHashMapに委任します。

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

MutablePointPointの代わりに元のクラスを使用した場合、getLocationsスレッドセーフではない変更可能な状態への参照を公開させることにより、カプセル化を解除することになります。Vehicle Trackerクラスの動作が少し変更されていることに注意してください。監視バージョンは場所のスナップショットを返しましたが、委任バージョンは変更できませんが、車両の場所の「ライブ」ビューを返します。つまり、スレッドAが呼び出しgetLocations、スレッドBが後でいくつかのポイントの位置を変更した場合、それらの変更はスレッドAに返されるマップに反映されます。

4.3.2。独立した状態変数

また、基礎となる状態変数が独立している限り、スレッドセーフを複数の基礎となる状態変数に委任することもできます。

VisualComponentリスト4.9は、クライアントがマウスおよびキーストロークイベントのリスナーを登録できるようにするグラフィカルコンポーネントです。イベントが発生したときに適切なリスナーを呼び出すことができるように、各タイプの登録済みリスナーのリストを維持します。ただし、一連のマウスリスナーとキーリスナーの間に関係はありません。2つは独立しているためVisualComponent、スレッドセーフの義務を2つの基になるスレッドセーフリストに委任できます。

リスト4.9 スレッドセーフティを複数の基になる状態変数に委任する。

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponentを使用しCopyOnWriteArrayListて各リスナーリストを格納します。これは、リスナーリストの管理(セクション5.2.3を参照)に特に適した、スレッドセーフなList実装です。各リストはスレッドセーフであり、一方の状態をもう一方の状態に結び付ける制約がないためVisualComponent、スレッドセーフの責任を基になるmouseListenerskeyListenersオブジェクトに委任できます。

4.3.3。委任が失敗した場合

ほとんどの複合クラスは、それほど単純ではありませんVisualComponent。コンポーネントの状態変数に関連する不変式があります。NumberRangeリスト4.10では、2つAtomicIntegersを使用して状態を管理していますが、追加の制約を課しています。最初の数が2番目の数以下であることです。

リスト4.10。不変条件を十分に保護しない番号範囲クラス。これを行わないでください。

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRangeスレッドセーフではありません。下限と上限を制約する不変式は保持されません。setLowerおよびsetUpper方法は、この不変を尊重しようとしますが、そう悪いください。setLowersetUpperはどちらもcheck-then-actシーケンスですが、アトミックにするために十分なロックを使用していません。数値の範囲が(0、10)であり、あるスレッドがsetLower(5)を呼び出している間に別のスレッドがを呼び出すsetUpper(4)場合、不運なタイミングで、両方がセッターのチェックに合格し、両方の変更が適用されます。その結果、範囲が(5、4)になり、無効な状態になります。したがって、基になるAtomicIntegerはスレッドセーフですが、複合クラスはそうではありません。基礎となる状態変数lowerupperは独立しておらずNumberRange、スレッドセーフをスレッドセーフな状態変数に単純に委譲することはできません。

NumberRange共通のロックでロワーとアッパーをガードするなど、ロックを使用して不変条件を維持することにより、スレッドセーフにすることができます。また、クライアントがその不変条件を覆すことを防ぐために、下限と上限の公開を回避する必要があります。

クラスが複合アクションを持っている場合NumberRange、デリゲートだけではスレッドセーフに適したアプローチではありません。これらの場合、複合アクション全体が基礎となる状態変数に委任されない限り、クラスは独自のロックを提供して複合アクションがアトミックであることを保証する必要があります。

クラスが複数の独立したスレッドセーフな状態変数で構成されていて、無効な状態遷移を伴う操作がない場合、スレッドの安全性を基になる状態変数に委任できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.