2011-01-11 7 views
4

Ich habe eine Klasse Instanzen, die in mehreren Threads verwendet wird. Ich aktualisiere mehrere Mitgliedsvariablen von einem Thread und lese die gleichen Mitgliedsvariablen von einem Thread. Was ist der richtige Weg, um die Fadensicherheit zu erhalten?Wie kann ich eine Variable sicher aus einem Thread lesen und von einem anderen ändern?

eg: 
    phthread_mutex_lock(&mutex1) 
    obj1.memberV1 = 1; 
    //unlock here? 

Sollte ich den Mutex hier entsperren? (Wenn ein anderer Thread jetzt auf die Obj1-Membervariablen 1 und 2 zugreift, sind die Daten, auf die zugegriffen wird, möglicherweise nicht korrekt, da MemberV2 noch nicht aktualisiert wurde. Wenn ich die Sperre jedoch nicht freigebe, blockiert der andere Thread möglicherweise, weil es zeitraubend ist . unter

//perform some time consuming operation which must be done before the assignment to memberV2 and after the assignment to memberV1 
    obj1.memberV2 = update field 2 from some calculation 
pthread_mutex_unlock(&mutex1) //should I only unlock here? 

Dank

Antwort

0

Wahrscheinlich das beste, was zu tun ist:

temp = //perform some time consuming operation which must be done before the assignment to memberV2 

pthread_mutex_lock(&mutex1) 
obj1.memberV1 = 1; 
obj1.memberV2 = temp; //result from previous calculation 
pthread_mutex_unlock(&mutex1) 
+0

Ich denke, dies könnte dazu führen, dass ein Update verloren geht, wenn Thread 1 den Beispielcode ausführt und Thread 2 updates 'memberV1 'zwischen Thread 1, der' temp 'zuweist und den Mutex eingibt. – finnw

+0

@finnw: Die Frage selbst schlägt vor, dass dies ein Leser-Schreiber-Szenario ist, also sollte es kein Problem mit den Datenänderungen geben, wenn der Schreiber dies nicht erwartet. –

+0

Schwer zu sagen, ohne mehr Details zu wissen. Ich denke, die eigentliche Frage ist: Benötigt "zeitaufwendiger Betrieb" Daten von "obj1"? Wenn ja, dann können Sie es wahrscheinlich nicht außerhalb des Schlosses tun. –

1

Ihre Verriegelung korrekt ist Sie sollten nicht die Sperre lösen früh gerade einen anderen Thread zu ermöglichen. zu gehen (denn das ist der andere Thread erlauben würde, das Objekt in einem inkonsistenten Zustand zu sehen.)

0

Was ich tun würde, getrennt ist, die Berechnung der Update:

temp = some calculation 
pthread_mutex_lock(&mutex1); 
obj.memberV1 = 1; 
obj.memberV2 = temp; 
pthread_mutex_unlock(&mutex1); 
1

Vielleicht wäre es besser, zu tun so etwas wie:

//perform time consuming calculation 
pthread_mutex_lock(&mutex1) 
    obj1.memberV1 = 1; 
    obj1.memberV2 = result; 
pthread_mutex_unlock(&mutex1) 

dieser setzt natürlich voraus, dass die bei der Berechnung verwendeten Werte werden nicht auf einem anderen Thread geändert werden.

1

Es ist schwer zu sagen, was Sie tun, dass Probleme verursacht. Das Mutex-Muster ist ziemlich einfach. Sie sperren den Mutex, greifen auf die freigegebenen Daten zu, entsperren den Mutex. Dies schützt die Daten, da der Mutex nur einen Thread gleichzeitig sperrt. Jeder Thread, der die Sperre nicht erhalten kann, muss warten, bis der Mutex entsperrt ist. Das Aufschließen weckt die Kellner auf. Sie werden dann kämpfen, um das Schloss zu erreichen. Verlierer gehen wieder schlafen. Die Zeit, die für das Aufwachen benötigt wird, beträgt möglicherweise mehrere ms oder mehr ab dem Zeitpunkt, an dem die Sperre aufgehoben wird. Stellen Sie sicher, dass Sie den Mutex immer wieder entsperren.

Stellen Sie sicher, dass Sie die Sperren nicht für längere Zeit gesperrt halten. Die meiste Zeit ist eine lange Zeit wie eine Mikrosekunde. Ich bevorzuge es, "ein paar Zeilen Code" zu behalten. Deshalb haben Leute vorgeschlagen, dass Sie die langlaufende Berechnung außerhalb des Schlosses durchführen. Der Grund dafür, dass Sie die Sperren nicht lange halten, ist, dass Sie die Häufigkeit erhöhen, mit der andere Threads die Sperre erreichen und sich drehen oder schlafen müssen, was die Leistung verringert. Sie erhöhen auch die Wahrscheinlichkeit, dass Ihr Thread beim Besitz der Sperre vorweggenommen ist, was bedeutet, dass die Sperre aktiviert ist, während dieser Thread schläft. Das ist noch schlechtere Leistung.

Threads, die eine Sperre nicht tun, müssen nicht schlafen. Drehen bedeutet, dass ein Thread, der auf einen gesperrten Mutex trifft, nicht schläft, sondern wiederholt durchläuft, um die Sperre für eine vordefinierte Zeitspanne zu testen, bevor sie aufgibt und schläft. Dies ist eine gute Idee, wenn Sie mehrere Cores oder Cores mit mehreren simultanen Threads verwenden. Mehrere aktive Threads bedeuten, dass zwei Threads den Code gleichzeitig ausführen können. Wenn die Sperre eine kleine Menge Code enthält, wird der Thread, der die Sperre erhalten hat, bald fertig sein. Der andere Thread muss nur ein paar Nanosekunden warten, bevor er die Sperre bekommt. Denken Sie daran, Ihren Thread zu schlafen ist ein Kontextwechsel und etwas Code, um Ihren Thread an die Kellner auf dem Mutex anzuhängen, haben alle Kosten. Sobald Ihr Thread schläft, müssen Sie noch einige Zeit warten, bevor der Scheduler ihn aufweckt. das könnte mehrere ms sein. Nachschlagen Spinlocks.

Wenn Sie nur einen Kern haben, dann wenn ein Thread auf eine Sperre stößt, bedeutet dies, dass ein anderer schlafender Thread die Sperre besitzt und egal wie lange Sie ihn drehen, wird er nicht entsperrt. Du würdest also ein Schloss benutzen, das einen Kellner sofort beherbergt, in der Hoffnung, dass der Thread, der das Schloss besitzt, aufwachen und enden wird.

Sie sollten davon ausgehen, dass ein Thread bei jeder Maschinencode-Anweisung ausgeschlossen werden kann. Außerdem sollten Sie davon ausgehen, dass es sich bei jeder Zeile von c-Code wahrscheinlich um viele Maschinencode-Anweisungen handelt. Das klassische Beispiel ist i ++. Dies ist eine Aussage in c, aber ein Lesen, ein Inkrement und ein Speichern in Maschinencode-Land.

Wenn Sie wirklich Wert auf Leistung legen, versuchen Sie zuerst, atomare Operationen zu verwenden. Mutexes als letztes Mittel. Die meisten Nebenläufigkeitsprobleme sind leicht mit atomaren Operationen zu lösen (google gcc atomare Operationen, um mit dem Lernen zu beginnen) und sehr wenige Probleme brauchen wirklich Mutexe. Mutexe sind viel langsamer.

Ihre freigegebenen Daten schützen, wo immer es geschrieben ist und wo es lesen ist. sonst ... bereite dich auf das Scheitern vor. Sie müssen gemeinsame Daten nicht in Zeiträumen schützen, in denen nur ein einzelner Thread aktiv ist.

Es ist oft nützlich, Ihre App mit 1 Thread sowie N Threads ausführen zu können. Auf diese Weise können Sie die Rennbedingungen einfacher debuggen.

Minimieren Sie die gemeinsamen Daten, die Sie mit Sperren schützen. Versuchen Sie, Daten in Strukturen zu organisieren, so dass ein einzelner Thread exklusiven Zugriff auf die gesamte Struktur erhalten kann (z. B. indem Sie eine einzelne gesperrte Flagge oder Versionsnummer oder beides setzen) und sich danach um nichts mehr kümmern müssen. Dann ist der Großteil des Codes nicht mit Sperren und Rennbedingungen überladen.

Funktionen, die letztendlich in freigegebene Variablen schreiben, sollten bis zum letzten Moment temporäre Variablen verwenden und dann die Ergebnisse kopieren. Der Compiler wird nicht nur besseren Code generieren, sondern auch Zugriffe auf gemeinsam genutzte Variablen, insbesondere die Änderung, verursachen Cache-Zeilen-Aktualisierungen zwischen L2 und dem Haupt-RAM und alle möglichen anderen Leistungsprobleme. Auch wenn Sie sich nicht um die Leistung kümmern, ignorieren Sie dies. Allerdings empfehle ich Ihnen, das Dokument "alles, was ein Programmierer über Speicher wissen sollte" zu googlen, wenn Sie mehr wissen möchten.

Wenn Sie eine einzelne Variable aus den gemeinsamen Daten lesen, müssen Sie wahrscheinlich nicht sperren, solange die Variable ein Integer-Typ und kein Mitglied eines Bitfeldes ist (Bitfeld-Mitglieder werden mit mehreren Anweisungen gelesen/geschrieben). . Lesen Sie über atomare Operationen nach. Wenn Sie mit mehreren Werten arbeiten müssen, benötigen Sie eine Sperre, um sicherzustellen, dass Sie die Version A eines Wertes nicht gelesen haben, die Vorbelegung erhalten haben und dann Version B des nächsten Wertes lesen. Gleiches gilt für das Schreiben.

Sie werden feststellen, dass Kopien von Daten, sogar Kopien von ganzen Strukturen nützlich sind. Sie können daran arbeiten, eine neue Kopie der Daten zu erstellen, und sie dann austauschen, indem Sie einen Zeiger mit einer atomaren Operation ändern. Sie können eine Kopie der Daten erstellen und dann Berechnungen anstellen, ohne sich Gedanken darüber machen zu müssen, ob sich die Daten ändern.

Also vielleicht, was Sie tun wollen, ist:

lock the mutex 
    Make a copy of the input data to the long running calculation. 
unlock the mutex 

L1: Do the calculation 

Lock the mutex 
    if the input data has changed and this matters 
    read the input data, unlock the mutex and go to L1 
    updata data 
unlock mutex 

Vielleicht oben in dem Beispiel, das Ergebnis noch speichern, wenn der Eingang verändert, aber zurück und recalc gehen. Es hängt davon ab, ob andere Threads eine etwas veraltete Antwort verwenden können. Vielleicht ändern andere Threads, wenn sie sehen, dass ein Thread bereits die Berechnung durchführt, einfach die Eingabedaten und belassen es dem beschäftigten Thread, um dies zu bemerken und die Berechnung zu wiederholen (es wird eine Race-Bedingung geben, wenn Sie das tun, und) Einfaches). Auf diese Weise können die anderen Threads andere Aufgaben erledigen als nur schlafen.

Prost.

Verwandte Themen