Machen Sie einen verknüpften Listen-Thread sicher

7

Ich weiß, dass das schon einmal gefragt wurde (und ich werde weiter forschen), aber ich muss wissen, wie man eine bestimmte verknüpfte Liste threadsicher macht. Mein aktuelles Problem ist, dass ich einen Thread habe, der alle Elemente in einer verknüpften Liste durchläuft, und einen anderen, der am Ende dieser Liste weitere Elemente hinzufügt. Manchmal kommt es vor, dass der eine Thread versucht, der Liste ein weiteres Element hinzuzufügen, während der erste gerade damit beschäftigt ist, ihn zu durchlaufen (was eine Ausnahme verursacht).

Ich dachte daran, einfach eine Variable hinzuzufügen (boolesches Flag), um zu sagen, dass die Liste gerade durchforstet wird, aber wie überprüfe ich sie und warte mit dem zweiten Thread (es ist in Ordnung, wenn es wartet, wie der erster Thread läuft ziemlich schnell). Die einzige Möglichkeit, dies zu tun, ist die Verwendung einer While-Schleife, die ständig diese belebte Flagge überprüft. Ich erkannte, dass dies eine sehr dumme Idee war, da es die CPU dazu bringen würde, hart zu arbeiten, während nichts nützliches getan wurde. Und jetzt bin ich hier, um nach einer besseren Einsicht zu fragen. Ich habe über Schlösser und so weiter gelesen, aber es scheint in meinem Fall nicht relevant zu sein, aber vielleicht liege ich falsch?

In der Zwischenzeit werde ich weiter im Internet suchen und zurückschreiben, wenn ich eine Lösung finde.

BEARBEITEN: Lassen Sie mich wissen, ob ich etwas Code veröffentlichen sollte, um die Dinge zu klären, aber ich werde versuchen, es klarer zu erklären.

Ich habe also eine Klasse mit einer verknüpften Liste, die Elemente enthält, die verarbeitet werden müssen. Ich habe einen Thread, der durch einen Funktionsaufruf durch diese Liste iteriert (nennen wir es "processElements"). Ich habe einen zweiten Thread, der Elemente hinzufügt, die auf nicht-deterministische Weise verarbeitet werden. Manchmal versucht es jedoch, diese addElement-Funktion aufzurufen, während die processElements ausgeführt wird. Dies bedeutet, dass ein Element der verknüpften Liste hinzugefügt wird, während es vom ersten Thread durchlaufen wird. Dies ist nicht möglich und verursacht eine Ausnahme. Hoffe das klärt auf.

Ich brauche den Thread, der neue Elemente hinzufügt, bis die processElements-Methode ausgeführt wird.

  • Jeder, der über dieses Problem stolpert. Die angenommene Antwort gibt Ihnen eine schnelle, einfache Lösung, aber werfen Sie einen Blick auf die Antwort von Brian Gideon unten für eine umfassendere Antwort, die Ihnen definitiv mehr Einblick geben wird!
Denzil 11.05.2012, 18:36
quelle

2 Antworten

17

Die Ausnahme ist wahrscheinlich darauf zurückzuführen, dass die Sammlung in der Mitte einer Iteration über IEnumerator geändert wurde. Es gibt einige Techniken, die Sie verwenden können, um die Thread-Sicherheit aufrechtzuerhalten. Ich werde sie in der Reihenfolge von schwierig präsentieren.

Alles sperren

Dies ist bei weitem die einfachste und einfachste Methode, um auf die threadsichere Datenstruktur zuzugreifen. Dieses Muster funktioniert gut, wenn die Anzahl der Lese- und Schreiboperationen gleich ist.

%Vor%

Muster kopieren / lesen

Dies ist ein etwas komplexeres Muster. Sie werden feststellen, dass vor dem Lesen eine Kopie der Datenstruktur erstellt wurde. Dieses Muster funktioniert gut, wenn die Anzahl der Lesevorgänge im Vergleich zur Anzahl der Schreibvorgänge gering ist und der Nachteil der Kopie relativ gering ist.

%Vor%

Kopieren-Modifizieren-Tauschen-Muster

Und schließlich haben wir das komplexeste und fehleranfälligste Muster. Ich empfehle eigentlich nicht, dieses Muster zu verwenden, es sei denn, Sie wirklich wissen, was Sie tun. Jede Abweichung von dem, was ich unten habe, könnte zu Problemen führen. Es ist leicht, dieses hier durcheinander zu bringen. In der Tat habe ich diesen auch versehentlich in der Vergangenheit verschraubt. Sie werden feststellen, dass vor allen Änderungen eine Kopie der Datenstruktur erstellt wurde. Die Kopie wird dann modifiziert und schließlich wird die Originalreferenz mit der neuen Instanz ausgetauscht. Grundsätzlich behandeln wir collection immer so, als wäre es unveränderlich. Dieses Muster funktioniert gut, wenn die Anzahl der Schreibvorgänge im Vergleich zur Anzahl der Lesevorgänge gering ist und der Nachteil der Kopie relativ gering ist.

%Vor%

Aktualisierung:

Also habe ich im Kommentarbereich zwei Fragen gestellt:

  • Warum lock(lockobj) anstelle von lock(collection) auf der Schreibseite?
  • Warum local = collection auf der Leseseite?

Betrachte die erste Frage, wie der C # -Compiler die lock erweitern wird.

%Vor%

Nun ist es hoffentlich leichter zu sehen, was schiefgehen kann, wenn wir collection als Lock-Ausdruck verwenden.

  • Thread A führt object temp = collection .
  • aus
  • Thread B führt collection = copy .
  • aus
  • Thread C führt object temp = collection .
  • aus
  • Thread A erwirbt die Sperre mit der ursprünglichen Referenz.
  • Thread C erwirbt die Sperre mit der neuen Referenz.

Das wäre eindeutig eine Katastrophe! Schreibvorgänge gehen verloren, da der kritische Abschnitt mehr als einmal eingegeben wird.

Nun war die zweite Frage ein wenig schwierig. Sie müssen das nicht unbedingt mit dem Code, den ich oben gepostet habe, machen. Das liegt daran, dass ich collection nur einmal verwendet habe. Betrachten Sie nun den folgenden Code.

%Vor%

Das Problem hier ist, dass collection jederzeit ausgelagert werden kann. Dies wäre ein unglaublich schwieriger Fehler zu finden. Aus diesem Grund extrahiere ich immer eine lokale Referenz auf der Leseseite, wenn ich dieses Muster mache. Das stellt sicher, dass wir die gleiche Sammlung bei jeder Leseoperation verwenden.

Da Probleme wie diese so subtil sind, empfehle ich dieses Muster nicht, es sei denn, Sie wirklich müssen.

    
Brian Gideon 11.05.2012, 19:00
quelle
5

Hier ist ein kurzes Beispiel für die Verwendung von Sperren zum Synchronisieren Ihres Zugriffs auf die Liste:

%Vor%

Eine lock(o) -Anweisung bedeutet, dass der ausführende Thread eine wechselseitige Ausschlusssperre für das Objekt o erhalten soll, den Anweisungsblock ausführen und schließlich die Sperre für o freigeben soll. Wenn ein anderer Thread versucht, gleichzeitig eine Sperre für o zu erhalten (entweder für denselben Codeblock oder für jede andere), wird er blockiert (warten), bis die Sperre aufgehoben wird.

Der entscheidende Punkt ist also, dass Sie dasselbe Objekt für alle lock -Anweisungen verwenden, die Sie synchronisieren möchten. Das tatsächliche Objekt, das Sie verwenden, kann beliebig sein, solange es konsistent ist. Im obigen Beispiel wurde unsere Sammlung als schreibgeschützt deklariert, sodass wir sie sicher als unser Schloss verwenden können. Wenn dies jedoch nicht der Fall ist, sollten Sie ein anderes Objekt sperren:

%Vor%     
Douglas 11.05.2012 18:48
quelle

Tags und Links