Was bedeutet threadsafe?

Kürzlich habe ich versucht, auf ein Textfeld aus einem Thread (außer dem UI-Thread) zuzugreifen, und eine Ausnahme wurde ausgetriggers. Es sagte etwas über den “Code, der nicht threadsicher ist” und so schrieb ich einen Delegaten (Probe von MSDN half) und nannte es stattdessen.

Aber ich habe nicht ganz verstanden, warum der zusätzliche Code notwendig war.

Update: Wird es zu ernsthaften Problemen kommen, wenn ich nachschaue?

Controls.CheckForIllegalCrossThread..blah =true 

   

Eric Lippert hat einen schönen Blogeintrag mit dem Titel ” Was ist das, was du” thread safe “nennst? über die Definition von Thread-Sicherheit wie von Wikipedia gefunden.

3 wichtige Dinge aus den Links extrahiert:

“Ein Code ist Thread-sicher, wenn er bei gleichzeitiger Ausführung durch mehrere Threads korrekt funktioniert.”

“Insbesondere muss es die Notwendigkeit für mehrere Threads erfüllen, um auf dieselben gemeinsamen Daten zuzugreifen, …”

“… und die Notwendigkeit eines gemeinsamen Datenbereichs, auf den zu einem bestimmten Zeitpunkt nur ein Thread zugreifen kann.”

Auf jeden Fall eine Lektüre wert!

Im einfachsten Fall bedeutet threadsafe, dass von mehreren Threads aus sicher zugegriffen werden kann. Wenn Sie mehrere Threads in einem Programm verwenden und beide versuchen, auf eine gemeinsame Datenstruktur oder Speicherposition zuzugreifen, können verschiedene schlimme Dinge passieren. Also fügen Sie 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 es dann threadsicher zu machen, müssen Sie Person 2 zwingen, auf die Erfüllung von Aufgabe 1 zu warten, bevor Person 2 das Dokument bearbeiten darf.

Wikipedia hat einen Artikel über Fadensicherheit.

Diese Definitionsseite (Sie müssen eine Anzeige überspringen – Entschuldigung) definiert es so:

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

Ein Thread ist ein Ausführungspfad eines Programms. Ein single-threaded-Programm wird nur einen Thread haben und dieses Problem tritt daher nicht auf. Praktisch alle GUI-Programme haben mehrere Ausführungswege und somit Threads – eines zum Verarbeiten der Anzeige der GUI und zum Übergeben von Benutzereingaben, andere zum tatsächlichen Ausführen der Operationen des Programms. Dies ist so, dass die Benutzeroberfläche immer noch reactjs, während das Programm arbeitet.

Ein Modul ist Thread-sicher, wenn es garantiert, dass es seine Invarianten angesichts der Verwendung von Multi-Threading und Concurrence beibehalten kann.

Hier kann ein Modul eine Datenstruktur, class, Objekt, Methode / Prozedur oder function sein. Im Grunde genommen ein Stück 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 es für alle Umgebungen gilt, dass der Code kompiliert und ausgeführt werden kann.

Thread-unsafe-Module funktionieren möglicherweise ordnungsgemäß unter Mehrfachnutzung und gleichzeitige Verwendung, aber das liegt oft mehr an Glück und Zufall, als sorgfältiges Design. Selbst wenn ein Modul unter Ihnen nicht kaputt geht, kann es brechen, wenn es in andere Umgebungen verschoben wird.

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

Threadsafe bedeutet einfach, dass eine Methode oder classninstanz 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; } 

Jetzt wollen Thread A und Thread B beide AddOne () ausführen. aber A startet zuerst und liest den Wert von myInt (0) in tmp ein. Aus irgendeinem Grund beschließt der Scheduler, Thread A anzuhalten und die Ausführung an 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 am Ende myInt = 1. Und 1 wird zurückgegeben. Jetzt ist wieder Thread A an der Reihe. Thread A wird fortgesetzt. Und fügt tmp 1 hinzu (tmp war 0 für Thread A). Und speichert dann diesen Wert in myInt. myInt ist wieder 1.

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

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, dh nur ein Thread kann diese Methode zu einem bestimmten Zeitpunkt ausführen. Die anderen Threads warten in der Schlange. Dies macht eine Methode Thread sicher, aber wenn es viel Arbeit in einer Methode getan werden muss, verschwendet dies viel Platz. Eine andere Technik besteht darin, ‘nur einen kleinen Teil einer Methode als synchronisiert’ zu markieren, indem eine Sperre oder ein Semaphor erzeugt und dieser kleine Teil gesperrt wird (normalerweise als kritischer Abschnitt bezeichnet). Es gibt sogar einige Methoden, die als lockless thread safe implementiert sind, was bedeutet, dass sie so konstruiert sind, dass mehrere Threads gleichzeitig durch sie hindurchlaufen können, ohne jemals Probleme zu verursachen. Dies kann der Fall sein, wenn nur eine Methode verwendet wird Führt einen atomaren Aufruf aus. Atomare Aufrufe sind Aufrufe, die nicht unterbrochen werden können und nur von jeweils einem Thread ausgeführt werden können.

Weitere Erklärungen finden Sie in dem Buch “Java Concurrency in Practice”:

Eine class ist Thread-sicher, wenn sie sich korrekt verhält, wenn auf sie von mehreren Threads zugegriffen wird, 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.

Thread-Sicherheit : Ein Thread-sicheres Programm schützt seine Daten vor Speicherkonsistenzerrorsn. In einem stark multi-threaded Programm verursacht ein threadsicheres Programm keine Nebenwirkungen mit mehreren Lese- / Schreiboperationen von mehreren Threads auf denselben Objekten. Verschiedene Threads können Objektdaten ohne Konsistenzerrors gemeinsam nutzen und ändern.

Sie können Thread-Sicherheit erreichen, indem Sie Advanced Concurrency API verwenden. Diese Dokumentationsseite bietet gute Programmierkonstrukte, um die Thread-Sicherheit zu erreichen.

Lock-Objekte unterstützen Sperr-Idiome, die viele gleichzeitige Anwendungen vereinfachen.

Executors definieren eine High-Level-API zum Starten und Verwalten von Threads. Executor-Implementierungen, die von java.util.concurrent bereitgestellt werden, stellen eine Thread-Pool-Verwaltung bereit, die für große Anwendungen geeignet ist.

Concurrent Collections vereinfacht das Verwalten großer Datensammlungen und kann den Bedarf an Synchronisation erheblich reduzieren.

Atomare Variablen verfügen über functionen, die die Synchronisierung minimieren und Speicherkonsistenzerrors vermeiden helfen.

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

Weitere Informationen zu anderen Programmierkonstrukten finden Sie in den Paketen java.util.concurrent und java.util.concurrent.atomic .

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

Nehmen wir an, Sie haben ein Bankkonto mit dem Internet und Mobile Banking und Ihr Konto hat nur 10 $. Sie haben das Überweisungsguthaben mit Mobile Banking auf ein anderes Konto übertragen und in der Zwischenzeit haben Sie mit demselben Bankkonto online eingekauft. Wenn dieser BankAccount nicht “GEWÄHRLEISTET” ist, dann erlaubt die Bank, dass Sie zwei Transaktionen durchführen, und dann wird die Bank bankrott.

ThreadSafe bedeutet, dass sich der Objektstatus nicht ändert, wenn mehrere Threads gleichzeitig versuchen, auf das Objekt zuzugreifen.

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 diese aktualisiert werden können. Aus diesem Grund finden Sie Beispiele auf MSDN und anderswo, die demonstrieren, wie der Aufruf zurück auf den Hauptthread geleitet wird.

Normale WinForms-Übung besteht darin, einen einzelnen Thread zu verwenden, der für alle Ihre UI-Arbeit bestimmt ist.

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

Zum Beispiel habe ich Code gesehen, der Fließkommazahlen zu Zeichenketten formatierte, wenn zwei davon in verschiedenen Threads ausgeführt werden, kann der globale Wert von decimalSeparator dauerhaft auf ‘.’

 //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 

Um die Threadsicherheit zu verstehen, lesen Sie die folgenden Abschnitte :

4.3.1. Beispiel: Fahrzeugverfolgung mit Delegierung

Als konkreteres Beispiel für die Delegierung können wir eine Version des Fahrzeug-Trackers erstellen, die an eine Thread-sichere class delegiert. Wir speichern die Standorte in einer Karte, daher beginnen wir mit einer threadsicheren Kartenimplementierung, ConcurrentHashMap . Wir speichern den Speicherort auch mithilfe einer unveränderlichen Point-class anstelle von MutablePoint (siehe Listing 4.6).

Listing 4.6. Immutable Point-class, 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; } } 

Point ist Thread-sicher, weil es unveränderlich ist. Unveränderbare Werte können frei freigegeben und veröffentlicht werden, sodass wir die Standorte nicht mehr kopieren müssen, wenn Sie sie zurückgeben.

DelegatingVehicleTracker in Listing 4.7 verwendet keine explizite Synchronisation; Der gesamte Zugriff auf den Status wird von ConcurrentHashMap , und alle Schlüssel und Werte der Map sind unveränderbar.

Listing 4.7. Thread-Sicherheit an eine ConcurrentHashMap delegieren.

  public class DelegatingVehicleTracker { private final ConcurrentMap locations; private final Map unmodifiableMap; public DelegatingVehicleTracker(Map points) { this.locations = new ConcurrentHashMap(points); this.unmodifiableMap = Collections.unmodifiableMap(locations); } public Map 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 MutablePoint class anstelle von Point verwendet hätten, würden wir die Kapselung getLocations , indem getLocations einen Verweis auf einen nicht threadsicheren getLocations veröffentlichen würde. Beachten Sie, dass wir das Verhalten der Fahrzeugverfolgungs-class leicht geändert haben. Während die Monitorversion einen Snapshot der Standorte zurückliefert, gibt die delegierende Version eine nicht änderbare aber “live” Ansicht der Fahrzeugstandorte zurück. Dies bedeutet, dass, wenn Thread A getLocations und 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 Threadsicherheit auch an mehr als eine zugrunde liegende Statusvariable delegieren, solange diese zugrundeliegenden Statusvariablen unabhängig sind, was bedeutet, dass die Composite-class keine Invarianten auferlegt, die die mehreren Statusvariablen betreffen.

VisualComponent in Listing 4.9 ist eine grafische Komponente, mit der Clients Listener für Maus- und Tastendruckereignisse registrieren können. Es verwaltet eine Liste registrierter Listener jedes Typs, sodass bei Auftreten eines Ereignisses die entsprechenden Listener aufgerufen werden können. Es gibt jedoch keine Beziehung zwischen dem Satz von Maus-Listenern und Schlüssel-Listenern; Die beiden sind unabhängig und daher kann VisualComponent seine VisualComponent an zwei zugrunde liegende threadsichere Listen delegieren.

Listing 4.9. Delegieren der Threadsicherheit an mehrere zugrunde liegende Statusvariablen.

 public class VisualComponent { private final List keyListeners = new CopyOnWriteArrayList(); private final List mouseListeners = new CopyOnWriteArrayList(); 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 verwendet eine CopyOnWriteArrayList , um jede Listener-Liste zu speichern. Dies ist eine threadsichere List-Implementierung, die sich besonders für die Verwaltung von Listener-Listen eignet (siehe Abschnitt 5.2.3). Jede Liste ist Thread-sicher, und da es keine Einschränkungen gibt, die den Status von einem mit dem Status des anderen VisualComponent kann VisualComponent seine Thread-Sicherheitsverantwortlichkeiten an die zugrunde liegenden mouseListeners und keyListeners Objekte keyListeners .

4.3.3. Wenn die Delegierung fehlschlägt

Die meisten zusammengesetzten classn sind nicht so einfach wie VisualComponent : Sie haben Invarianten, die ihre Komponentenstatusvariablen in Beziehung setzen. NumberRange in Listing 4.10 verwendet zwei AtomicIntegers , um seinen AtomicIntegers zu verwalten, AtomicIntegers jedoch eine zusätzliche Einschränkung hinzu – die erste Zahl ist kleiner oder gleich der zweiten.

Listing 4.10. Nummernbereichsklasse, 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()); } } 

NumberRange ist nicht Thread-sicher ; Es bewahrt nicht die Invariante, die die unteren und oberen Grenzen einschränkt. Die Methoden setLower und setUpper versuchen, diese Invariante zu respektieren, tun dies jedoch nur schlecht. Sowohl setLower als auch setUpper sind check-then-act-Sequenzen, aber sie verwenden keine ausreichende Sperre, um sie atomar zu machen. Wenn der Nummernbereich (0, 10) gilt und ein Thread setLower(5) aufruft, während ein anderer Thread setUpper(4) aufruft, werden setUpper(4) mit einem unglücklichen Timing die Prüfungen in den Setter bestehen und beide Modifikationen werden angewendet. Das Ergebnis ist, dass der Bereich jetzt (5, 4) gilt - ein ungültiger Zustand . Während die zugrundeliegenden AtomicInteger Thread-sicher sind, ist die Composite-class nicht . Da die zugrunde liegenden Statusvariablen " NumberRange und " upper nicht unabhängig sind, kann NumberRange Thread-Sicherheit nicht einfach an seine Thread-sicheren Statusvariablen delegieren.

NumberRange kann thread-sicher gemacht werden, indem das Sperren verwendet wird, um seine Invarianten beizubehalten, z. B. das Schützen von unteren und oberen NumberRange mit einer gemeinsamen Sperre. Es muss auch vermieden werden, niedrigere und höhere Versionen zu veröffentlichen, um zu verhindern, dass Clients ihre Invarianten unterlaufen.

Wenn eine class zusammengesetzte Aktionen hat, wie es NumberRange tut, ist die Delegierung allein wiederum kein geeigneter Ansatz für die Thread-Sicherheit. In diesen Fällen muss die class 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 class aus mehreren unabhängigen threadsicheren Statusvariablen besteht und keine Operationen mit ungültigen Statusübergängen aufweist, kann sie die Threadsicherheit an die zugrunde liegenden Statusvariablen delegieren.