Was macht eine Methode threadsicher? Wie lauten die Regeln?

156

Gibt es allgemeine Regeln / Richtlinien dafür, was eine Methode threadsicher macht? Ich verstehe, dass es wahrscheinlich eine Million einmaliger Situationen gibt, aber was ist im Allgemeinen? Ist es so einfach?

  1. Wenn eine Methode nur auf lokale Variablen zugreift, ist sie threadsicher.

Ist es das? Gilt das auch für statische Methoden?

Eine Antwort von @Cybis lautete:

Lokale Variablen können nicht von Threads gemeinsam genutzt werden, da jeder Thread seinen eigenen Stapel erhält.

Gilt das auch für statische Methoden?

Wenn einer Methode ein Referenzobjekt übergeben wird, bricht dies die Thread-Sicherheit? Ich habe einige Nachforschungen angestellt, und es gibt eine Menge über bestimmte Fälle, aber ich hatte gehofft, mit nur wenigen Regeln Richtlinien definieren zu können, die befolgt werden müssen, um sicherzustellen, dass eine Methode threadsicher ist.

Meine letzte Frage lautet also: "Gibt es eine kurze Liste von Regeln, die eine thread-sichere Methode definieren? Wenn ja, welche?"

BEARBEITEN
Hier wurden viele gute Punkte gemacht. Ich denke, die eigentliche Antwort auf diese Frage lautet: "Es gibt keine einfachen Regeln, um die Thread-Sicherheit zu gewährleisten." Cool. Fein. Aber im Allgemeinen denke ich, dass die akzeptierte Antwort eine gute, kurze Zusammenfassung liefert. Es gibt immer Ausnahmen. So sei es. Ich kann damit leben.

Bob Horn
quelle
59
Du sollst nicht auf Variablen zugreifen, auf die auch andere Threads ohne Sperre zugreifen.
Hans Passant
4
Hanth Pathant hat sich in einen Igor verwandelt!
Martin James
3
Außerdem .. 'Du sollst nicht auf Variablen zugreifen, auf die auch andere Threads ohne Sperre zugreifen' - es spielt keine Rolle, ob der gelesene Wert gelegentlich nicht der neueste ist oder tatsächlich grob falsch ist.
Martin James
Hier ist ein schöner Blog von Eric, der dich in einen Wirbelwind versetzt.
RBT

Antworten:

139

Wenn eine Methode (Instanz oder statisch) nur auf Variablen verweist, die in dieser Methode enthalten sind, ist sie threadsicher, da jeder Thread seinen eigenen Stapel hat:

In diesem Fall können mehrere Threads ThreadSafeMethodgleichzeitig ohne Probleme aufgerufen werden.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

Dies gilt auch, wenn die Methode eine andere Klassenmethode aufruft, die nur auf Variablen mit lokalem Gültigkeitsbereich verweist:

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

Wenn eine Methode auf Eigenschaften oder Felder (Objektstatus) (Instanz oder statisch) zugreift, müssen Sie Sperren verwenden, um sicherzustellen, dass die Werte nicht von einem anderen Thread geändert werden.

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

Sie sollten sich bewusst sein, dass alle an die Methode übergebenen Parameter, die weder eine Struktur noch unveränderlich sind, von einem anderen Thread außerhalb des Bereichs der Methode mutiert werden können.

Um eine ordnungsgemäße Parallelität sicherzustellen, müssen Sie die Sperrung verwenden.

Weitere Informationen finden Sie in der C # -Referenz der Sperranweisung und in ReadWriterLockSlim .

Die Sperre ist meistens nützlich, um Funktionen nacheinander
ReadWriterLockSlimbereitzustellen. Sie ist nützlich, wenn Sie mehrere Leser und einzelne Schreiber benötigen.

Trevor Pilley
quelle
15
Im dritten Beispiel private string someValue;ist nicht staticso, dass jede Instanz eine separate Kopie dieser Variablen erhält. Können Sie bitte erklären, warum dies nicht threadsicher ist?
Bharadwaj
29
@ Bharadwaj, wenn es eine Instanz der ThingKlasse gibt, auf die mehrere Threads zugreifen
Trevor Pilley
111

Wenn eine Methode nur auf lokale Variablen zugreift, ist sie threadsicher. Ist es das?

Absolut nicht. Sie können ein Programm mit nur einer lokalen Variablen schreiben, auf die von einem einzelnen Thread aus zugegriffen wird, der jedoch nicht threadsicher ist:

https://stackoverflow.com/a/8883117/88656

Gilt das auch für statische Methoden?

Absolut nicht.

Eine Antwort von @Cybis lautete: "Lokale Variablen können nicht von Threads gemeinsam genutzt werden, da jeder Thread seinen eigenen Stapel erhält."

Absolut nicht. Das Unterscheidungsmerkmal einer lokalen Variablen besteht darin, dass sie nur innerhalb des lokalen Bereichs sichtbar ist und nicht im temporären Pool zugeordnet ist . Es ist völlig legal und möglich, von zwei verschiedenen Threads auf dieselbe lokale Variable zuzugreifen. Sie können dazu anonyme Methoden, Lambdas, Iteratorblöcke oder asynchrone Methoden verwenden.

Gilt das auch für statische Methoden?

Absolut nicht.

Wenn einer Methode ein Referenzobjekt übergeben wird, bricht dies die Thread-Sicherheit?

Vielleicht.

Ich habe einige Nachforschungen angestellt, und es gibt eine Menge über bestimmte Fälle, aber ich hatte gehofft, mit nur wenigen Regeln Richtlinien definieren zu können, die befolgt werden müssen, um sicherzustellen, dass eine Methode threadsicher ist.

Sie müssen lernen, mit Enttäuschung zu leben. Dies ist ein sehr schwieriges Thema.

Meine letzte Frage lautet also: "Gibt es eine kurze Liste von Regeln, die eine thread-sichere Methode definieren?

Nee. Wie Sie in meinem Beispiel zuvor gesehen haben, kann eine leere Methode nicht threadsicher sein . Sie können auch fragen, ob es eine kurze Liste von Regeln gibt, die sicherstellen, dass eine Methode korrekt ist . Nein, da ist kein. Die Gewindesicherheit ist nichts anderes als eine äußerst komplizierte Art der Korrektheit.

Darüber hinaus weist die Tatsache, dass Sie die Frage stellen, auf Ihr grundlegendes Missverständnis hinsichtlich der Thread-Sicherheit hin. Die Thread-Sicherheit ist eine globale und keine lokale Eigenschaft eines Programms. Der Grund, warum es so schwierig ist, das Richtige zu finden, liegt darin, dass Sie das Threading-Verhalten des gesamten Programms vollständig kennen müssen, um seine Sicherheit zu gewährleisten.

Schauen Sie sich noch einmal mein Beispiel an: Jede Methode ist trivial . Es ist die Art und Weise, wie die Methoden auf einer "globalen" Ebene miteinander interagieren, die das Programm zum Stillstand bringt. Sie können nicht jede Methode betrachten und als "sicher" abhaken und dann erwarten, dass das gesamte Programm sicher ist, genauso wenig wie Sie daraus schließen können, dass Ihr Haus auch aus 100% nicht hohlen Ziegeln besteht nicht hohl. Die Hohlheit eines Hauses ist ein globales Eigentum des Ganzen, kein Aggregat der Eigenschaften seiner Teile.

Eric Lippert
quelle
14
Kernaussage: Thread-Sicherheit ist eine globale, keine lokale Eigenschaft eines Programms.
Greg D
5
@ BobHorn: class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } Rufen Sie M () auf und rufen Sie dann C.getter und C.setter in zwei verschiedenen Threads auf. Die lokale Variable kann jetzt in zwei verschiedene Threads geschrieben und daraus gelesen werden, obwohl es sich um eine lokale Variable handelt. Nochmals: Das definierende Merkmal einer lokalen Variablen ist, dass sie lokal ist und nicht auf dem Stapel des Threads .
Eric Lippert
3
@ BobHorn: Ich denke, es hat mehr damit zu tun, unsere Werkzeuge auf einer Ebene zu verstehen, auf der wir mit Autorität und Wissen darüber sprechen können. Beispielsweise verriet die ursprüngliche Frage ein Unverständnis darüber, was eine lokale Variable ist. Dieser Fehler ist ziemlich häufig, aber Tatsache ist, dass es sich um einen Fehler handelt und korrigiert werden sollte. Die Definition einer lokalen Variablen ist sehr genau und sollte entsprechend behandelt werden. :) Dies ist keine Antwort für "Menschen", die nicht wissen, dass es pathologische Fälle gibt. Dies ist eine Klarstellung, damit Sie verstehen können, was Sie tatsächlich gefragt haben. :)
Greg D
7
@EricLippert: In der MSDN-Dokumentation für viele Klassen wird erklärt, dass öffentliche statische (in Visual Basic freigegebene) Mitglieder dieses Typs threadsicher sind. Es wird nicht garantiert, dass Instanzmitglieder threadsicher sind. ' oder manchmal 'Dieser Typ ist threadsicher.' (zB String), wie können diese Garantien gegeben werden, wenn die Thread-Sicherheit ein globales Anliegen ist?
Mike Zboray
3
@ Mikez: Gute Frage. Entweder mutieren diese Methoden keinen Status, oder wenn sie dies tun, mutieren sie den Status sicher ohne Sperren, oder wenn sie Sperren verwenden, tun sie dies auf eine Weise, die garantiert, dass die globale "Sperrreihenfolge" nicht verletzt wird. Strings sind unveränderliche Datenstrukturen, deren Methoden nichts mutieren, sodass Strings leicht sicher gemacht werden können.
Eric Lippert
11

Es gibt keine feste Regel.

Hier sind einige Regeln, um den Code-Thread in .NET sicher zu machen und warum dies keine guten Regeln sind:

  1. Die Funktion und alle aufgerufenen Funktionen müssen rein sein (keine Nebenwirkungen) und lokale Variablen verwenden. Obwohl dies Ihren Code threadsicher macht, gibt es auch sehr wenige interessante Dinge, die Sie mit dieser Einschränkung in .NET tun können.
  2. Jede Funktion, die mit einem gemeinsamen Objekt arbeitet, muss lockmit einer gemeinsamen Sache arbeiten. Alle Sperren müssen in derselben Reihenfolge erfolgen. Dadurch wird der Code-Thread sicher, aber er ist unglaublich langsam, und Sie können genauso gut nicht mehrere Threads verwenden.
  3. ...

Es gibt keine Regel, die den Code-Thread sicher macht. Sie können nur sicherstellen, dass Ihr Code funktioniert, egal wie oft er aktiv ausgeführt wird. Jeder Thread kann zu jedem Zeitpunkt unterbrochen werden, wobei sich jeder Thread befindet seinen eigenen Zustand / Ort, und dies für jede Funktion (statisch oder anderweitig), die auf allgemeine Objekte zugreift.

EarlNameless
quelle
10
Schlösser müssen nicht langsam sein. Schlösser sind unglaublich schnell; Eine unbestrittene Sperre liegt in der Größenordnung von zehn bis hundert Nanosekunden . Umstrittene Sperren sind natürlich willkürlich langsam; Wenn Sie verlangsamt werden, weil Sie Sperren anfechten, entwickeln Sie das Programm neu, um den Konflikt zu entfernen .
Eric Lippert
2
Ich glaube, ich habe # 2 nicht klar genug gemacht. Ich habe versucht zu sagen, dass es eine Möglichkeit gibt, Threads sicher zu machen: Sperren Sie jeden Zugriff auf allgemeine Objekte. Unter der Annahme, dass es mehrere Threads gibt, wird dies zu Konflikten führen, und Sperren verlangsamen das Ganze. Beim Hinzufügen von Sperren kann man nicht einfach blind Sperren hinzufügen, sie müssen an sehr guten strategischen Stellen platziert werden.
EarlNameless