Monitor gegen Schloss

88

Wann ist es angemessen, entweder die MonitorKlasse oder das lockSchlüsselwort für die Thread-Sicherheit in C # zu verwenden?

EDIT: Aus den bisherigen Antworten geht hervor, dass dies lockeine kurze Hand für eine Reihe von Anrufen an die MonitorKlasse ist. Wofür genau ist der Lock Call Shorthand? Oder expliziter:

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

Aktualisieren

Vielen Dank für Ihre Hilfe: Ich habe eine weitere Frage als Folge einiger der von Ihnen bereitgestellten Informationen gestellt. Da Sie sich in diesem Bereich gut auskennen, habe ich den Link gepostet: Was ist falsch an dieser Lösung zum Sperren und Verwalten gesperrter Ausnahmen?

Smartcaveman
quelle

Antworten:

89

Eric Lippert spricht in seinem Blog darüber: Sperren und Ausnahmen passen nicht zusammen

Der entsprechende Code unterscheidet sich zwischen C # 4.0 und früheren Versionen.


In C # 4.0 ist es:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    { body }
}
finally
{
    if (lockWasTaken) Monitor.Exit(temp);
}

Es setzt Monitor.Entervoraus, dass das Flag atomar gesetzt wird, wenn die Sperre aufgehoben wird.


Und früher war es:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

Dies hängt davon ab, dass keine Ausnahme zwischen Monitor.Enterund geworfen wird try. Ich denke, im Debug-Code wurde diese Bedingung verletzt, weil der Compiler ein NOP zwischen ihnen eingefügt und so einen Thread-Abbruch zwischen diesen möglich gemacht hat.

CodesInChaos
quelle
Wie ich bereits sagte, ist das erste Beispiel C # 4 und das andere wird in früheren Versionen verwendet.
CodesInChaos
Als Randnotiz erwähnt C # über CLR eine Einschränkung des Schlüsselworts lock: Möglicherweise möchten Sie häufig etwas tun, um den beschädigten Status (falls zutreffend) wiederherzustellen, bevor Sie die Sperre aufheben. Da das Schlüsselwort lock nicht zulässt, dass Dinge in den catch-Block eingefügt werden, sollten wir in Betracht ziehen, die Langversion try-catch-finally für nicht triviale Routinen zu schreiben.
Kizzx2
5
IMO, das den gemeinsam genutzten Zustand wiederherstellt, ist orthogonal zum Sperren / Multithreading. Also sollte es mit einem Try-Catch / endlich im lockBlock gemacht werden.
CodesInChaos
2
@ kizzx2: Ein solches Muster wäre besonders schön bei Reader-Writer-Sperren. Wenn innerhalb von Code, der eine Lesersperre enthält, eine Ausnahme auftritt, besteht kein Grund zu der Annahme, dass die geschützte Ressource beschädigt werden könnte, und daher kein Grund, sie ungültig zu machen. Wenn eine Ausnahme innerhalb einer Writer-Sperre auftritt und der Code für die Ausnahmebehandlung nicht ausdrücklich anzeigt, dass der Status des geschützten Objekts repariert wurde, deutet dies darauf hin, dass das Objekt möglicherweise beschädigt ist und ungültig gemacht werden sollte. IMHO, unerwartete Ausnahmen sollten ein Programm nicht zum Absturz bringen, sondern alles ungültig machen, was möglicherweise beschädigt ist.
Supercat
2
@ArsenZahray Sie brauchen kein Pulseeinfaches Sperren. Dies ist in einigen fortgeschrittenen Multithreading-Szenarien wichtig. Ich habe noch nie Pulsedirekt verwendet.
CodesInChaos
43

lockist nur eine Abkürzung für Monitor.Entermit try+ finallyund Monitor.Exit. Verwenden Sie die lock-Anweisung, wann immer dies ausreicht. Wenn Sie TryEnter benötigen, müssen Sie Monitor verwenden.

Lukáš Novotný
quelle
23

Eine lock-Anweisung entspricht:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Beachten Sie jedoch, dass Monitor auch Wait () und Pulse () ausführen kann , die häufig in komplexen Multithreading-Situationen hilfreich sind.

Aktualisieren

In C # 4 ist es jedoch anders implementiert:

bool lockWasTaken = false;
var temp = obj;
try 
{
     Monitor.Enter(temp, ref lockWasTaken); 
     //your code
}
finally 
{ 
     if (lockWasTaken) 
             Monitor.Exit(temp); 
} 

Vielen Dank an CodeInChaos für Kommentare und Links

Shekhar_Pro
quelle
In C # 4 ist die lock-Anweisung anders implementiert. blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
CodesInChaos
13

Monitorist flexibler. Mein bevorzugter Anwendungsfall für die Verwendung von Monitor ist, wenn Sie nicht auf Ihren Zug warten und einfach überspringen möchten :

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}
Alex
quelle
6

Wie andere gesagt haben, lockist "gleichwertig" mit

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Aber nur aus Neugier lockwird der erste Hinweis, den Sie ihm geben , erhalten bleiben und nicht geworfen, wenn Sie ihn ändern. Ich weiß, dass es nicht empfohlen wird, das gesperrte Objekt zu ändern, und Sie möchten es nicht tun.

Aber auch für die Wissenschaft funktioniert das gut:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

... und das nicht:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

Error:

Eine Ausnahme vom Typ 'System.Threading.SynchronizationLockException' trat in 70783sTUDIES.exe auf, wurde jedoch nicht im Benutzercode behandelt

Zusätzliche Informationen: Die Objektsynchronisationsmethode wurde aus einem nicht synchronisierten Codeblock aufgerufen.

Dies liegt daran Monitor.Exit(lockObject);, dass auf etwas reagiert wird, lockObjectdas sich geändert hat, weil stringses unveränderlich ist. Dann rufen Sie es aus einem nicht synchronisierten Codeblock auf. Aber trotzdem. Dies ist nur eine lustige Tatsache.

André Pena
quelle
"Dies liegt daran, dass Monitor.Exit (lockObject) auf lockObject einwirkt." Dann macht lock nichts mit dem Objekt? Wie funktioniert die Sperre?
Yugo Amaryl
@YugoAmaryl, ich nehme an, es liegt daran, dass die lock-Anweisung zuerst die übergebene Referenz berücksichtigt und sie dann verwendet, anstatt eine geänderte Referenz zu verwenden, wie:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.
4

Beides ist dasselbe. lock ist ein scharfes Schlüsselwort und verwendet die Monitor-Klasse.

http://msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx

RobertoBr
quelle
3
Schauen Sie sich msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx an. "Tatsächlich wird das Schlüsselwort lock mit der Monitor-Klasse implementiert. Zum Beispiel"
RobertoBr
1
Die zugrunde liegende Implementierung von lock verwendet Monitor, aber sie sind nicht dasselbe. Berücksichtigen Sie die von monitor bereitgestellten Methoden, die für lock nicht vorhanden sind, und die Art und Weise, wie Sie in separaten Codeblöcken sperren und entsperren können.
eran otzap
3

Die Sperre und das grundlegende Verhalten des Monitors (Enter + Exit) sind mehr oder weniger gleich, aber der Monitor verfügt über mehr Optionen, die Ihnen mehr Synchronisationsmöglichkeiten ermöglichen.

Das Schloss ist eine Verknüpfung und die Option für die grundlegende Verwendung.

Wenn Sie mehr Kontrolle benötigen, ist der Monitor die bessere Option. Sie können Wait, TryEnter und Pulse für erweiterte Verwendungen (wie Barrieren, Semaphoren usw.) verwenden.

Borja
quelle
0

Zusätzlich zu allen obigen Erklärungen ist lock eine C # -Anweisung, während Monitor eine Klasse von .NET ist, die sich im System.Threading-Namespace befindet.

PureSilence
quelle
0

Sperren Schlüsselwort Lock stellt sicher, dass ein Thread gleichzeitig einen Code ausführt.

lock (lockObject)

        {
        //   Body
        }

Das Schlüsselwort lock markiert einen Anweisungsblock als kritischen Abschnitt, indem die Sperre für den gegenseitigen Ausschluss für ein bestimmtes Objekt abgerufen, eine Anweisung ausgeführt und dann die Sperre aufgehoben wird

Wenn ein anderer Thread versucht, einen gesperrten Code einzugeben, wartet er blockiert, bis das Objekt freigegeben wird.

Monitor Der Monitor ist eine statische Klasse und gehört zum System.Threading-Namespace.

Es bietet eine exklusive Sperre für das Objekt, sodass zu einem bestimmten Zeitpunkt nur ein Thread in den kritischen Abschnitt eintreten kann.

Unterschied zwischen Monitor und Sperre in C #

Die Sperre ist die Verknüpfung für Monitor. Geben Sie try und finally ein. Sperrgriffe versuchen und blockieren schließlich intern Lock = Monitor + versuchen es schließlich.

Wenn Sie mehr Kontrolle mit erweiterten Multithreading - Lösungen zu implementieren TryEnter() Wait(), Pulse()und PulseAll()Methoden, dann ist die Monitor - Klasse Ihre Wahl.

C # Monitor.wait(): Ein Thread wartet darauf, dass andere Threads benachrichtigt werden.

Monitor.pulse(): Ein Thread benachrichtigt einen anderen Thread.

Monitor.pulseAll(): Ein Thread benachrichtigt alle anderen Threads innerhalb eines Prozesses

Aatrey
quelle