Was bedeutet threadsafe?

123

Kürzlich habe ich versucht, von einem Thread (außer dem UI-Thread) auf ein Textfeld zuzugreifen, und es wurde eine Ausnahme ausgelöst. Es sagte etwas über den "Code ist nicht threadsicher" aus und so schrieb ich einen Delegaten (Beispiel von MSDN half) und rief ihn stattdessen auf.

Trotzdem verstand ich nicht ganz, warum der ganze zusätzliche Code notwendig war.

Update: Treten bei einer Überprüfung ernsthafte Probleme auf?

Controls.CheckForIllegalCrossThread..blah =true
Vivek Bernard
quelle
5
In der Regel bedeutet "thread-sicher", was auch immer die Person, die den Begriff verwendet, zumindest für diese Person bedeutet. Als solches ist es kein sehr nützliches Sprachkonstrukt - Sie müssen viel, viel spezifischer sein, wenn Sie über das Verhalten von Thread-Code sprechen.
7
Duplizieren?: Stackoverflow.com/questions/261683/…
Dave O.
@ Dave Tut mir leid, ich habe versucht zu suchen, aber aufgegeben ... trotzdem danke ..
Vivek Bernard
1
ein Code, der nicht entstehtRace-Condition
Muhammad Babar

Antworten:

121

Eric Lippert hat einen schönen Blog-Beitrag mit dem Titel Was nennst du "thread safe"? über die Definition der Thread-Sicherheit aus Wikipedia.

3 wichtige Dinge aus den Links extrahiert:

"Ein Code ist threadsicher, wenn er bei gleichzeitiger Ausführung durch mehrere Threads ordnungsgemäß funktioniert."

"Insbesondere muss es die Notwendigkeit erfüllen, dass mehrere Threads auf dieselben gemeinsam genutzten Daten zugreifen, ..."

"... und die Notwendigkeit, dass immer nur ein Thread auf ein gemeinsames Datenelement zugreifen kann."

Auf jeden Fall eine Lektüre wert!

Gregory Pakosz
quelle
24
Bitte vermeiden Sie nur Link-Antworten, da diese in Zukunft jederzeit schlecht werden können.
akhil_mittal
aktualisierter Link: docs.microsoft.com/en-nz/archive/blogs/ericlippert/…
Ryan Buddicom
106

Im einfachsten Sinne bedeutet threadsafe, dass der Zugriff von mehreren Threads sicher ist. Wenn Sie mehrere Threads in einem Programm verwenden und diese jeweils versuchen, auf eine gemeinsame Datenstruktur oder einen Speicherort im Speicher zuzugreifen, können mehrere schlechte Dinge passieren. Sie fügen also zusätzlichen Code hinzu, um diese schlechten Dinge zu verhindern. Wenn beispielsweise zwei Personen gleichzeitig dasselbe Dokument schreiben, überschreibt die zweite zu speichernde Person die Arbeit der ersten Person. Um den Thread dann sicher zu machen, müssen Sie Person 2 zwingen, darauf zu warten, dass Person 1 ihre Aufgabe erledigt, bevor Person 2 das Dokument bearbeiten kann.

Vincent Ramdhanie
quelle
11
Dies wird als Synchronisation bezeichnet. Richtig?
JavaTechnical
3
Ja. Das Erzwingen, dass die verschiedenen Threads auf den Zugriff auf eine gemeinsam genutzte Ressource warten, kann durch Synchronisierung erreicht werden.
Vincent Ramdhanie
In Gregorys akzeptierter Antwort heißt es: "Ein Teil des Codes ist threadsicher, wenn er bei gleichzeitiger Ausführung durch mehrere Threads ordnungsgemäß funktioniert." während Sie sagen "Um den Thread sicher zu machen, müssen Sie Person 1 zum Warten zwingen"; sagt er nicht, dass Simultan akzeptabel ist, während Sie sagen, dass dies nicht der Fall ist? Können Sie das bitte erklären?
Honey
Das ist gleich. Ich schlage nur einen einfachen Mechanismus als Beispiel dafür vor, was Code threadsicher macht. Unabhängig vom verwendeten Mechanismus sollten sich jedoch mehrere Threads, die denselben Code ausführen, nicht gegenseitig stören.
Vincent Ramdhanie
Gilt dies also nur für Code, der globale und statische Variablen verwendet? Anhand Ihres Beispiels für Personen, die Dokumente bearbeiten, ist es vermutlich nicht sinnvoll, Person 2 daran zu hindern, den Code zum Schreiben von Dokumenten für ein anderes Dokument auszuführen.
Aaron Franke
18

Wikipedia hat einen Artikel über Thread-Sicherheit.

Diese Definitionsseite (Sie müssen eine Anzeige überspringen - sorry) definiert sie folgendermaßen:

In der Computerprogrammierung beschreibt thread-safe einen Programmteil oder eine Routine, die von mehreren Programmierthreads ohne unerwünschte Interaktion zwischen den Threads aufgerufen werden kann.

Ein Thread ist ein Ausführungspfad eines Programms. Ein einzelnes Thread-Programm hat nur einen Thread, sodass dieses Problem nicht auftritt. Praktisch alle GUI-Programme haben mehrere Ausführungspfade und damit Threads - es gibt mindestens zwei, einen zum Verarbeiten der Anzeige der GUI und zum Übergeben von Benutzereingaben und mindestens einen zum tatsächlichen Ausführen der Programmoperationen.

Dies geschieht, damit die Benutzeroberfläche während der Arbeit des Programms weiterhin reagiert, indem lang laufende Prozesse auf Threads außerhalb der Benutzeroberfläche verlagert werden. Diese Threads können einmal erstellt werden und für die gesamte Lebensdauer des Programms vorhanden sein oder bei Bedarf erstellt und nach Abschluss zerstört werden.

Da diese Threads häufig allgemeine Aktionen ausführen müssen - Festplatten-E / A, Ausgabe von Ergebnissen auf dem Bildschirm usw. - müssen diese Teile des Codes so geschrieben werden, dass sie von mehreren Threads aufgerufen werden können, häufig um die selbe Zeit. Dies beinhaltet Dinge wie:

  • Arbeiten an Kopien von Daten
  • Hinzufügen von Sperren um den kritischen Code
ChrisF
quelle
8

Thread-sicher bedeutet einfach, dass eine Methode oder Klasseninstanz von mehreren Threads gleichzeitig verwendet werden kann, ohne dass Probleme auftreten.

Betrachten Sie die folgende Methode:

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

Nun möchten sowohl Thread A als auch Thread B AddOne () ausführen. aber A startet zuerst und liest den Wert von myInt (0) in tmp. Aus irgendeinem Grund beschließt der Scheduler, Thread A anzuhalten und die Ausführung auf Thread B zu verschieben. Thread B liest jetzt auch den Wert von myInt (immer noch 0) in seine eigene Variable tmp. Thread B beendet die gesamte Methode, also ist am Ende myInt = 1. Und 1 wird zurückgegeben. Jetzt ist Thread A wieder an der Reihe. Thread A wird fortgesetzt. Und addiert 1 zu tmp (tmp war 0 für Thread A). Und speichert diesen Wert dann in myInt. myInt ist wieder 1.

In diesem Fall wurde die Methode AddOne zweimal aufgerufen. Da die Methode jedoch nicht threadsicher implementiert wurde, ist der Wert von myInt nicht wie erwartet 2, sondern 1, da der zweite Thread die Variable myInt gelesen hat, bevor der erste Thread beendet wurde Aktualisierung.

Das Erstellen threadsicherer Methoden ist in nicht trivialen Fällen sehr schwierig. Und es gibt einige Techniken. In Java können Sie eine Methode als synchronisiert markieren. Dies bedeutet, dass jeweils nur ein Thread diese Methode ausführen kann. Die anderen Threads warten in der Schlange. Dies macht einen Methoden-Thread sicher, aber wenn in einer Methode viel Arbeit zu erledigen ist, verschwendet dies viel Platz. Eine andere Technik besteht darin, "nur einen kleinen Teil einer Methode als synchronisiert zu markieren".durch Erstellen einer Sperre oder eines Semaphors und Sperren dieses kleinen Teils (normalerweise als kritischer Abschnitt bezeichnet). Es gibt sogar einige Methoden, die als sperrenlos Thread-sicher implementiert sind. Dies bedeutet, dass sie so aufgebaut sind, dass mehrere Threads gleichzeitig durch sie laufen können, ohne jemals Probleme zu verursachen. Dies kann nur bei einer Methode der Fall sein führt einen atomaren Aufruf aus. Atomic Calls sind Anrufe, die nicht unterbrochen werden können und jeweils nur von einem Thread ausgeführt werden können.

Sujith PS
quelle
wenn Methode AddOne zweimal aufgerufen wurde
Sujith PS
6

In der realen Welt ist ein Beispiel für den Laien

Angenommen, Sie haben ein Bankkonto bei Internet und Mobile Banking und Ihr Konto hat nur 10 US-Dollar. Sie haben das Überweisungsguthaben mithilfe von Mobile Banking auf ein anderes Konto übertragen und in der Zwischenzeit Online-Einkäufe über dasselbe Bankkonto getätigt. Wenn dieses Bankkonto nicht threadsicher ist, können Sie mit der Bank zwei Transaktionen gleichzeitig ausführen, und die Bank wird bankrott.

Threadsafe bedeutet, dass sich der Status eines Objekts nicht ändert, wenn gleichzeitig mehrere Threads versuchen, auf das Objekt zuzugreifen.

Yasir Shabbir Choudhary
quelle
5

Weitere Erklärungen finden Sie im Buch "Java Concurrency in Practice":

Eine Klasse ist threadsicher, wenn sie sich beim Zugriff von mehreren Threads korrekt verhält, unabhängig von der Planung oder Verschachtelung der Ausführung dieser Threads durch die Laufzeitumgebung und ohne zusätzliche Synchronisation oder andere Koordination seitens des aufrufenden Codes.

Jacky
quelle
4

Ein Modul ist threadsicher, wenn es garantiert, dass es seine Invarianten angesichts der Verwendung von Multithreads und der gleichzeitigen Verwendung beibehalten kann.

Hier kann ein Modul eine Datenstruktur, eine Klasse, ein Objekt, eine Methode / Prozedur oder eine Funktion sein. Grundsätzlich Umfang Code und zugehörige Daten.

Die Garantie kann möglicherweise auf bestimmte Umgebungen wie eine bestimmte CPU-Architektur beschränkt sein, muss jedoch für diese Umgebungen gelten. Wenn es keine explizite Abgrenzung von Umgebungen gibt, wird normalerweise angenommen, dass für alle Umgebungen gilt, dass der Code kompiliert und ausgeführt werden kann.

Thread-unsichere Module funktionieren möglicherweise bei gleichzeitiger Verwendung mit mehreren Threads ordnungsgemäß. Dies ist jedoch häufig eher auf Glück und Zufall als auf sorgfältiges Design zurückzuführen. Selbst wenn ein Modul für Sie unter nicht beschädigt wird, kann es beim Verschieben in andere Umgebungen beschädigt werden.

Multithreading-Fehler sind oft schwer zu debuggen. Einige von ihnen kommen nur gelegentlich vor, während andere sich aggressiv manifestieren - auch dies kann umgebungsspezifisch sein. Sie können sich als subtil falsche Ergebnisse oder Deadlocks manifestieren. Sie können Datenstrukturen auf unvorhersehbare Weise durcheinander bringen und dazu führen, dass andere scheinbar unmögliche Fehler in anderen entfernten Teilen des Codes auftreten. Es kann sehr anwendungsspezifisch sein, daher ist es schwierig, eine allgemeine Beschreibung zu geben.

Chris Vest
quelle
3

Thread-Sicherheit : Ein Thread-sicheres Programm schützt seine Daten vor Speicherkonsistenzfehlern. In einem Programm mit mehreren Threads verursacht ein threadsicheres Programm keine Nebenwirkungen bei mehreren Lese- / Schreibvorgängen von mehreren Threads für dieselben Objekte. Verschiedene Threads können Objektdaten ohne Konsistenzfehler gemeinsam nutzen und ändern.

Sie können die Thread-Sicherheit mithilfe der erweiterten Parallelitäts-API erreichen. Diese Dokumentation Seite bietet eine gute Programmierkonstrukte Thread - Sicherheit zu erreichen.

Sperrobjekte unterstützen Sperrsprachen, die viele gleichzeitige Anwendungen vereinfachen.

Ausführende definieren eine allgemeine API zum Starten und Verwalten von Threads. Von java.util.concurrent bereitgestellte Executor-Implementierungen bieten eine Thread-Pool-Verwaltung, die für umfangreiche Anwendungen geeignet ist.

Gleichzeitige Sammlungen erleichtern die Verwaltung großer Datensammlungen und können den Synchronisierungsbedarf erheblich reduzieren.

Atomic Variables verfügen über Funktionen, die die Synchronisation minimieren und Speicherkonsistenzfehler vermeiden.

ThreadLocalRandom (in JDK 7) bietet eine effiziente Generierung von Pseudozufallszahlen aus mehreren Threads.

Weitere Programmierkonstrukte finden Sie auch in den Paketen java.util.concurrent und java.util.concurrent.atomic .

Ravindra Babu
quelle
1

Sie arbeiten eindeutig in einer WinForms-Umgebung. WinForms-Steuerelemente weisen eine Thread-Affinität auf. Dies bedeutet, dass der Thread, in dem sie erstellt werden, der einzige Thread ist, mit dem auf sie zugegriffen und sie aktualisiert werden können. Aus diesem Grund finden Sie auf MSDN und anderswo Beispiele, die zeigen, wie der Rückruf auf den Hauptthread zurückgeführt wird.

Die normale WinForms-Praxis besteht darin, einen einzelnen Thread zu haben, der für alle Ihre UI-Arbeiten vorgesehen ist.

David M.
quelle
1

Ich finde, dass das Konzept von http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 das ist, was ich normalerweise als unsicheres Threading betrachte, wenn eine Methode einen Nebeneffekt wie eine globale Variable hat und darauf beruht.

Ich habe zum Beispiel Code gesehen, der Gleitkommazahlen in Zeichenfolgen formatiert hat. Wenn zwei davon in unterschiedlichen Threads ausgeführt werden, kann der globale Wert von decimalSeparator dauerhaft in 'geändert werden.

//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
Aaron Robson
quelle
-2

Lesen Sie die folgenden Abschnitte, um die Thread-Sicherheit zu verstehen :

4.3.1. Beispiel: Vehicle Tracker mit Delegation

Als umfangreicheres Beispiel für die Delegierung erstellen wir eine Version des Fahrzeug-Trackers, die an eine thread-sichere Klasse delegiert. Wir speichern die Standorte in einer Karte und beginnen daher mit einer thread-sicheren Kartenimplementierung ConcurrentHashMap. Wir speichern den Speicherort auch mit einer unveränderlichen Punktklasse anstelle von MutablePointListing 4.6.

Listing 4.6. Unveränderliche Punktklasse, die von DelegatingVehicleTracker verwendet wird.

 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;
    }

}

Pointist threadsicher, weil es unveränderlich ist. Unveränderliche Werte können frei geteilt und veröffentlicht werden, sodass wir die Speicherorte bei der Rückgabe nicht mehr kopieren müssen.

DelegatingVehicleTrackerin Listing 4.7 wird keine explizite Synchronisation verwendet; Der gesamte Zugriff auf den Status wird von verwaltet ConcurrentHashMap, und alle Schlüssel und Werte der Karte sind unveränderlich.

Listing 4.7. Delegieren der Thread-Sicherheit an eine 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);
        }
    }

}}

Wenn wir die ursprüngliche MutablePointKlasse anstelle von Point verwendet hätten, würden wir die Kapselung unterbrechen getLocations, indem wir einen Verweis auf einen veränderlichen Zustand veröffentlichen lassen, der nicht threadsicher ist. Beachten Sie, dass wir das Verhalten der Fahrzeug-Tracker-Klasse geringfügig geändert haben. Während die Monitorversion eine Momentaufnahme der Standorte zurückgibt, gibt die delegierende Version eine nicht veränderbare, aber "Live" -Ansicht der Fahrzeugstandorte zurück. Dies bedeutet, dass wenn Thread A aufruft getLocationsund Thread B später die Position einiger Punkte ändert, diese Änderungen in der Map widergespiegelt werden, die an Thread A zurückgegeben wird.

4.3.2. Unabhängige Zustandsvariablen

Wir können die Thread-Sicherheit auch an mehr als eine zugrunde liegende Zustandsvariable delegieren, solange diese zugrunde liegenden Zustandsvariablen unabhängig sind. Dies bedeutet, dass die zusammengesetzte Klasse keine Invarianten auferlegt, an denen mehrere Zustandsvariablen beteiligt sind.

VisualComponentin Listing 4.9 ist eine grafische Komponente, mit der Clients Listener für Maus- und Tastenanschlagereignisse registrieren können. Es wird eine Liste der registrierten Listener jedes Typs geführt, sodass bei Auftreten eines Ereignisses die entsprechenden Listener aufgerufen werden können. Es gibt jedoch keine Beziehung zwischen den Maushörern und den Tastenhörern. Die beiden sind unabhängig und können daher VisualComponentihre Thread-Sicherheitsverpflichtungen an zwei zugrunde liegende Thread-sichere Listen delegieren.

Listing 4.9. Delegieren der Thread-Sicherheit an mehrere zugrunde liegende Statusvariablen.

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);
    }

}

VisualComponentverwendet a CopyOnWriteArrayList, um jede Listenerliste zu speichern; Dies ist eine thread-sichere Listenimplementierung, die sich besonders zum Verwalten von Listener-Listen eignet (siehe Abschnitt 5.2.3). Jede Liste ist threadsicher. Da es keine Einschränkungen gibt, die den Status der einen mit dem Status der anderen VisualComponentkoppeln , kann sie ihre Thread-Sicherheitsverantwortung an den Basiswert mouseListenersund die keyListenersObjekte delegieren .

4.3.3. Wenn die Delegierung fehlschlägt

Die meisten zusammengesetzten Klassen sind nicht so einfach wie VisualComponent: Sie haben Invarianten, die ihre Komponentenzustandsvariablen in Beziehung setzen. NumberRangeIn Listing 4.10 werden zwei verwendet AtomicIntegers, um den Status zu verwalten. Es wird jedoch eine zusätzliche Einschränkung auferlegt: Die erste Zahl muss kleiner oder gleich der zweiten sein.

Listing 4.10. Zahlenbereichsklasse, die ihre Invarianten nicht ausreichend schützt. Tu das nicht.

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());
    }

}

NumberRangeist nicht threadsicher ; Die Invariante, die die unteren und oberen Grenzen einschränkt, bleibt nicht erhalten. Die Methoden setLowerund setUpperversuchen, diese Invariante zu respektieren, tun dies jedoch schlecht. Beide setLowerund setUppersind Check-Then-Act-Sequenzen, verwenden jedoch keine ausreichende Verriegelung, um sie atomar zu machen. Wenn der Nummernkreis gilt (0, 10) und ein Thread anruft, setLower(5)während ein anderer Thread anruft setUpper(4), bestehen beide mit etwas unglücklichem Timing die Prüfungen in den Setzern und beide Modifikationen werden angewendet. Das Ergebnis ist, dass der Bereich jetzt (5, 4) enthält - ein ungültiger Zustand . So , während die zugrunde liegenden AtomicIntegers Thread-sicher sind, ist die zusammengesetzte Klasse nicht . Weil die zugrunde liegenden Zustandsvariablen lowerunduppersind nicht unabhängig, NumberRangekönnen die Thread-Sicherheit nicht einfach an ihre thread-sicheren Statusvariablen delegieren.

NumberRangekönnte fadensicher gemacht werden, indem Verriegelungen verwendet werden, um die Invarianten aufrechtzuerhalten, z. B. das Schützen der unteren und oberen mit einer gemeinsamen Verriegelung. Es muss auch vermieden werden, untere und obere zu veröffentlichen, um zu verhindern, dass Clients ihre Invarianten untergraben.

Wenn eine Klasse zusammengesetzte Aktionen NumberRangehat, ist die Delegierung allein wiederum kein geeigneter Ansatz für die Thread-Sicherheit. In diesen Fällen muss die Klasse eine eigene Sperre bereitstellen, um sicherzustellen, dass zusammengesetzte Aktionen atomar sind, es sei denn, die gesamte zusammengesetzte Aktion kann auch an die zugrunde liegenden Statusvariablen delegiert werden.

Wenn eine Klasse aus mehreren unabhängigen threadsicheren Statusvariablen besteht und keine Operationen mit ungültigen Statusübergängen aufweist, kann sie die Thread-Sicherheit an die zugrunde liegenden Statusvariablen delegieren.

Übertausch
quelle