2009-02-02 4 views
42

Dies ist eine Detailfrage für C#.C# Thread Sicherheit mit get/set

Angenommen, ich eine Klasse mit einem Gegenstand haben, und das Objekt wird durch eine Sperre geschützt:

Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     return property; 
    } 
    set { 
     property = value; 
    } 
} 

ich ein Abrufthread diese Eigenschaft abfragen zu können, wollen. Ich möchte auch, dass der Thread die Eigenschaften dieses Objekts gelegentlich aktualisiert, und manchmal kann der Benutzer diese Eigenschaft aktualisieren, und der Benutzer möchte diese Eigenschaft sehen können.

Blockiert der folgende Code die Daten ordnungsgemäß?

Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     lock (mLock){ 
      return property; 
     } 
    } 
    set { 
     lock (mLock){ 
       property = value; 
     } 
    } 
} 

von ‚richtig‘, was ich meine ist, wenn ich

MyProperty.Field1 = 2; 

oder was auch immer, wird anrufen will das Feld gesperrt werden, während ich das Update? Ist die Einstellung, die durch den Operator equals im Bereich der Funktion 'get' vorgenommen wird, oder wird die Funktion 'get' (und damit die Sperre) zuerst beendet und dann die Einstellung und dann 'set' aufgerufen und damit umgangen das Schloss?

Edit: Da dies offenbar nicht den Trick tun wird, was wird? Muss ich etwas wie tun:

Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     MyObject tmp = null; 
     lock (mLock){ 
      tmp = property.Clone(); 
     } 
     return tmp; 
    } 
    set { 
     lock (mLock){ 
       property = value; 
     } 
    } 
} 

, die mehr oder weniger nur dafür sorgt, dass ich nur Zugriff auf eine Kopie haben, dass heißt, wenn ich zwei Threads haben waren nennen zugleich ‚get‘, Sie würden jeweils mit dem gleichen Wert von Field1 beginnen (richtig?). Gibt es eine Möglichkeit, Lese- und Schreibsperren für eine Eigenschaft auszuführen, die Sinn macht? Oder sollte ich mich darauf beschränken, Teile von Funktionen und nicht die Daten selbst zu sperren?

Nur so, dass dieses Beispiel sinnvoll ist: MyObject ist ein Gerätetreiber, der Status asynchron zurückgibt. Ich sende Befehle über eine serielle Schnittstelle, und dann reagiert das Gerät auf diese Befehle in seiner eigenen süßen Zeit. Im Moment habe ich einen Thread, der ihn nach seinem Status abfragt ("Bist du noch da? Kannst du Befehle annehmen?"), Ein Thread, der auf Antworten auf dem seriellen Port wartet ("Habe Status String 2, alles ist gut")), und dann der UI-Thread, der andere Befehle ("Benutzer möchte, dass Sie dies tun.") und sendet die Antworten vom Treiber ("Ich habe gerade die Sache gemacht, jetzt die UI damit aktualisieren"). Deshalb möchte ich das Objekt selbst und nicht die Felder des Objekts sperren. das wäre eine große Anzahl von Sperren, a und b, nicht jedes Gerät dieser Klasse hat das gleiche Verhalten, nur allgemeines Verhalten, also müsste ich viele individuelle Dialoge programmieren, wenn ich die Sperren individualisierte.

Antwort

34

Nein, Ihr Code wird den Zugriff auf die Mitglieder des Objekts, das von MyProperty zurückgegeben wird, nicht sperren. Es sperrt nur MyProperty selbst.

Ihr Beispiel Nutzung ist wirklich zwei Operationen in einem, entspricht in etwa folgendermaßen aus:

// object is locked and then immediately released in the MyProperty getter 
MyObject o = MyProperty; 

// this assignment isn't covered by a lock 
o.Field1 = 2; 

// the MyProperty setter is never even called in this example 

Auf den Punkt gebracht - wenn zwei Threads MyProperty gleichzeitig zugreifen, der Getter wird kurz die zweite Thread blockiert, bis es die zurück Objekt zum ersten Thread, , aber es wird dann das Objekt auch an den zweiten Thread zurückgeben. Beide Threads haben dann vollen, nicht gesperrten Zugriff auf das Objekt.

EDIT in Reaktion auf weitere Einzelheiten in der Frage

Ich bin immer noch nicht zu 100% sicher, was Sie erreichen wollen, aber wenn Sie nur wollen Atom-Zugriff auf das Objekt dann Könnten Sie nicht die aufrufende Code-Sperre für das Objekt selbst haben?

// quick and dirty example 
// there's almost certainly a better/cleaner way to do this 
lock (MyProperty) 
{ 
    // other threads can't lock the object while you're in here 
    MyProperty.Field1 = 2; 
    // do more stuff if you like, the object is all yours 
} 
// now the object is up-for-grabs again 

Nicht ideal, aber so lange, wie alle Zugriff auf das Objekt in lock (MyProperty) Abschnitten enthalten ist, dann wird dieser Ansatz Thread-sicher sein.

+0

Einverstanden. Ich habe eine Beispiellösung für das, was Sie als Antwort auf eine Singleton Frage fragen http://stackoverflow.com/questions/7095/is-the-c-static-constructor-thread-safe/7105#7105 – Zooba

+0

ja, ich ' Ich werde wahrscheinlich mit dem Sperren des Objekts gehen, wie Sie beschrieben haben. Vielen Dank. – mmr

+0

Guter Punkt, aber wenn er eine 'MyProperty' namens' p' hätte und zwei Threads gleichzeitig aufruft: 'p = new MyObject (12)' und 'p = new MyObject (5)' dann würde * dieser * Zugriff synchronisiert werden . Allerdings würde das 'Field1'-Mitglied niemals gesperrt werden. Ich bin mir ziemlich sicher, dass du das sagst, ich versuche es nur für mich selbst zu verstehen. – Snoopy

1

In dem Codebeispiel, das Sie gepostet haben, wird ein get nie durchgeführt.

In einem komplizierteren Beispiel:

MyProperty.Field1 = MyProperty.doSomething() + 2; 

Und natürlich vorausgesetzt, Sie ein tat:

lock (mLock) 
{ 
    // stuff... 
} 

In doSomething() dann alle Verriegelungs Anrufe würden nicht ausreichen Synchronisation zu gewährleisten, über das gesamte Objekt. Sobald die Funktion doSomething() zurückkehrt, ist die Sperre verloren, dann erfolgt die Addition, und dann erfolgt die Zuweisung, die erneut sperrt.

Oder anders schreiben Sie wie die Schlösser so tun können, sind nicht amutomatically getan, und schreiben Sie diese mehr wie „Maschinencode“ mit einer Operation pro Zeile, und es wird klar:

lock (mLock) 
{ 
    val = doSomething() 
} 
val = val + 2 
lock (mLock) 
{ 
    MyProperty.Field1 = val 
} 
+0

hunh. Vielleicht missverstehe ich Eigenschaften, aber der Debugger sieht sicher so aus, als ob er in die 'get'-Funktion einsteigt, besonders wenn ich dort einen Haltepunkt für eine einfache Zuweisung setze. – mmr

+0

Ich gebe zu, ich bin mir nicht 100% sicher, dass es keinen get aufrufen, aber ich sehe keinen Grund, warum es würde. Wenn es einen get aufruft, gilt dasselbe, was ich gesagt habe, ersetzen Sie einfach doSomething() durch Ihre get-Funktion. – SoapBox

+0

Es ruft jedes Mal ein Get auf. Wo sonst käme die Referenz her? –

1

Die Das Schöne am Multithreading ist, dass Sie nicht wissen, in welcher Reihenfolge die Dinge passieren werden. Wenn Sie etwas in einem Thread festlegen, kann es passieren, dass es zuerst nach dem get passiert.

Der Code, den Sie gepostet haben, sperrt das Mitglied, während es gelesen und geschrieben wird. Wenn Sie den Fall behandeln möchten, in dem der Wert aktualisiert wird, sollten Sie möglicherweise andere Formen der Synchronisierung wie events untersuchen. (Überprüfen Sie die auto/manuellen Versionen). Dann können Sie Ihrem "Umfrage" -Thread mitteilen, dass sich der Wert geändert hat und bereit ist, erneut gelesen zu werden.

2

Der Sperrbereich in Ihrem Beispiel befindet sich an der falschen Stelle - er muss im Bereich der Eigenschaft 'MyObject' und nicht im Container enthalten sein.

Wenn die MyObject meine Objektklasse einfach verwendet wird, um Daten zu enthalten, auf die ein Thread schreiben möchte, und einen anderen (den UI-Thread), benötigen Sie möglicherweise überhaupt keinen Setter und konstruieren ihn einmal.

Berücksichtigen Sie auch, ob das Setzen von Sperren auf der Eigenschaftsebene die Schreibstufe der Sperrgranularität ist; Wenn mehr als eine Eigenschaft geschrieben werden kann, um den Status einer Transaktion darzustellen (zB: Gesamtbestellungen und Gesamtgewicht), ist es möglicherweise besser, die Sperre auf der MyObject-Ebene zu haben (zB lock (myObject.SyncRoot)). .)

0

In Ihrer bearbeiteten Version bieten Sie immer noch keine threadsafe Möglichkeit, MyObject zu aktualisieren. Änderungen an den Eigenschaften des Objekts müssen in einem synchronisierten/gesperrten Block vorgenommen werden.

Sie können einzelne Setter schreiben, um damit umzugehen, aber Sie haben angegeben, dass dies wegen der großen Zahlenfelder schwierig sein wird.Wenn es tatsächlich der Fall ist (und Sie haben noch nicht genügend Informationen zur Verfügung gestellt, um dies zu beurteilen), besteht eine Alternative darin, einen Setter zu schreiben, der die Reflexion verwendet; Dadurch können Sie eine Zeichenfolge übergeben, die den Feldnamen darstellt, und Sie können den Feldnamen dynamisch nachschlagen und den Wert aktualisieren. Dies würde Ihnen erlauben, einen einzelnen Setter zu haben, der auf einer beliebigen Anzahl von Feldern arbeiten würde. Dies ist nicht so einfach oder effizient, aber es würde Ihnen erlauben, mit einer großen Anzahl von Klassen und Feldern umzugehen.

12

Concurrent-Programmierung wäre ziemlich einfach, wenn Ihre Vorgehensweise funktionieren könnte. Aber es funktioniert nicht, der Eisberg, der sinkt, dass Titanic ist zum Beispiel der Kunde Ihrer Klasse dies zu tun:

objectRef.MyProperty += 1; 

Die Lese-Modifikations-Schreib-Rennen ist ziemlich offensichtlich, es gibt schlimmere. Es gibt absolut nichts, was Sie tun können, um Ihr Eigentum threadsicher zu machen, außer es unveränderlich zu machen. Es ist Ihr Klient, der mit den Kopfschmerzen fertig werden muss. Es ist die Achillesferse der gleichzeitigen Programmierung, diese Art von Verantwortung an einen Programmierer zu delegieren, der es am wenigsten schafft.

+0

wäre es nicht toll, wenn es so einfach sein könnte? Ich bin mir sicher, dass es Gründe gibt, warum es nicht so ist, aber ich kenne die Details nicht genug, um zu wissen, warum der Code nicht so funktioniert. – mmr

+0

Es ist wichtig, dies zu verstehen, versuchen Sie nicht gleichzeitige Programmierung, wenn Sie dies nicht tun. Google "atomare Updates". –

+0

Ich verstehe Atomizität, aber ich bin unklar, wo Dinge in C# atomar sind. Nur die grundlegenden Aufgaben, richtig? Atomicity mit etwas wie Interlocked zu spezifizieren könnte nützlich sein ... – mmr

4

Wie andere darauf hingewiesen haben, verlieren Sie die Kontrolle darüber, wer wann auf das Objekt zugreift, sobald Sie das Objekt vom Getter zurückgeben. Um das zu tun, was Sie tun möchten, müssen Sie eine Sperre in das Objekt selbst setzen.

Vielleicht verstehe ich nicht das ganze Bild, aber basierend auf Ihrer Beschreibung klingt es nicht so, als müssten Sie für jedes einzelne Feld eine Sperre haben. Wenn Sie eine Reihe von Feldern haben, die einfach über die Getter und Setter gelesen und geschrieben werden, könnten Sie wahrscheinlich mit einer einzigen Sperre für diese Felder auskommen. Es gibt offensichtlich das Potenzial, dass Sie die Operation Ihrer Threads auf diese Weise unnötigerweise serialisieren. Aber wiederum, basierend auf Ihrer Beschreibung, klingt es nicht so, als würden Sie aggressiv auf das Objekt zugreifen.

Ich würde auch vorschlagen, ein Ereignis zu verwenden, anstatt einen Thread zu verwenden, um den Gerätestatus abzufragen. Mit dem Abfragemechanismus werden Sie das Schloss jedes Mal schlagen, wenn der Thread das Gerät abfragt. Mit dem Ereignismechanismus würde das Objekt, sobald sich der Status ändert, alle Listener benachrichtigen. An diesem Punkt würde Ihr "Umfrage" -Thread (der nicht mehr abgefragt werden würde) aufwachen und den neuen Status erhalten. Dies wird viel effizienter sein.

Als Beispiel ...

public class Status 
{ 
    private int _code; 
    private DateTime _lastUpdate; 
    private object _sync = new object(); // single lock for both fields 

    public int Code 
    { 
     get { lock (_sync) { return _code; } } 
     set 
     { 
      lock (_sync) { 
       _code = value; 
      } 

      // Notify listeners 
      EventHandler handler = Changed; 
      if (handler != null) { 
       handler(this, null); 
      } 
     } 
    } 

    public DateTime LastUpdate 
    { 
     get { lock (_sync) { return _lastUpdate; } } 
     set { lock (_sync) { _lastUpdate = value; } } 
    } 

    public event EventHandler Changed; 
} 

Ihr 'Polling' Thread würde wie folgt aussehen.

+0

Interessant. Ich muss mir das ansehen. Mein erster Eindruck ist, dass es nicht funktioniert, weil das Gerät, das ich verwende, keine Statusmeldung ausgibt, wenn nicht gefragt wird. Wenn es also im "Prep" -Modus ist, gibt es keinen Hinweis, es sei denn, Sie fragen. – mmr

+0

Ja, wenn Sie das Gerät ansteuern müssen, um den Status zu melden, dann ist das Polling wahrscheinlich das einzige, was Sie tun können. Gibt es zufällig einen Callback-Mechanismus, der das Gerät veranlasst, den Status periodisch zu melden? Ich hasse Umfrage, weil es ineffizient ist, aber manchmal haben Sie keine Wahl. –

+0

Leider ist es ein echtes serielles Gerät - es reagiert nur auf Abfragen. Dies ist kein USB, das ist 9-polig. Old school retro den ganzen Weg, schätze ich. – mmr

-1

Does C aus den gleichen Problemen mit Sperren, wie andere Sprachen # Sperren nicht, dann leiden?

E.G.

var someObj = -1; 

// Thread 1 

if (someObj = -1) 
    lock(someObj) 
     someObj = 42; 

// Thread 2 

if (someObj = -1) 
    lock(someObj) 
     someObj = 24; 

Dies könnte das Problem haben, dass beide Threads schließlich ihre Sperren bekommen und den Wert ändern. Dies könnte zu einigen seltsamen Fehlern führen. Sie möchten das Objekt jedoch nicht unnötig sperren, es sei denn, Sie müssen es tun. In diesem Fall sollten Sie die doppelt geprüfte Verriegelung in Betracht ziehen.

// Threads 1 & 2 

if (someObj = -1) 
    lock(someObj) 
     if(someObj = -1) 
      someObj = {newValue}; 

Nur etwas zu beachten.