Monitoraggio vs blocco


88

Quando è appropriato utilizzare la Monitorclasse o la lockparola chiave per la sicurezza dei thread in C #?

EDIT: Dalle risposte finora sembra che locksia breve per una serie di chiamate alla Monitorclasse. A cosa serve esattamente la chiamata di blocco? O più esplicitamente,

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 ?
    }
}

Aggiornare

Grazie a tutti per il vostro aiuto: ho pubblicato un'altra domanda come seguito ad alcune delle informazioni che avete fornito. Dato che sembri essere esperto in questo settore, ho pubblicato il link: Cosa c'è di sbagliato in questa soluzione per bloccare e gestire le eccezioni bloccate?

Risposte:


89

Eric Lippert ne parla nel suo blog: serrature ed eccezioni non si mescolano

Il codice equivalente è diverso tra C # 4.0 e le versioni precedenti.


In C # 4.0 è:

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

Si basa sull'impostazione Monitor.Enteratomica della bandiera quando viene preso il blocco.


E prima era:

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

Ciò non si basa sul fatto che venga generata alcuna eccezione tra Monitor.Entere try. Penso che nel codice di debug questa condizione sia stata violata perché il compilatore ha inserito un NOP tra di loro e quindi ha reso possibile l'aborto del thread tra quelli possibili.


Come ho detto, il primo esempio è C # 4 e l'altro è quello che usano le versioni precedenti.
CodesInChaos

Come nota a margine, C # tramite CLR menziona un avvertimento sulla parola chiave lock: spesso potresti voler fare qualcosa per ripristinare lo stato danneggiato (se applicabile) prima di rilasciare il blocco. Dal momento che la parola chiave lock non ci permette di mettere cose nel blocco catch, dovremmo considerare di scrivere la versione lunga try-catch-latest per routine non banali.
kizzx2

5
IMO che ripristina lo stato condiviso è ortogonale al blocco / multi-threading. Quindi dovrebbe essere fatto con un try-catch / finalmente all'interno del lockblocco.
CodesInChaos

2
@ kizzx2: un tale schema sarebbe particolarmente carino con i blocchi lettore-scrittore. Se si verifica un'eccezione all'interno del codice che contiene un blocco del lettore, non c'è motivo di aspettarsi che la risorsa protetta possa essere danneggiata, e quindi nessun motivo per invalidarla. Se si verifica un'eccezione all'interno di un blocco di scrittura e il codice di gestione delle eccezioni non indica espressamente che lo stato dell'oggetto protetto è stato riparato, ciò suggerirebbe che l'oggetto potrebbe essere danneggiato e dovrebbe essere invalidato. IMHO, le eccezioni impreviste non dovrebbero mandare in crash un programma, ma dovrebbero invalidare tutto ciò che potrebbe essere danneggiato.
supercat

2
@ArsenZahray Non è necessario Pulseun semplice blocco. È importante in alcuni scenari avanzati di multi-threading. Non l'ho mai usato Pulsedirettamente.
CodesInChaos

43

lockè solo una scorciatoia per Monitor.Entercon try+ finallye Monitor.Exit. Usa l'istruzione lock ogni volta che è sufficiente: se hai bisogno di qualcosa come TryEnter, dovrai usare Monitor.


23

Un'istruzione lock è equivalente a:

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

Tuttavia, tieni presente che Monitor può anche Wait () e Pulse () , che sono spesso utili in complesse situazioni di multithreading.

Aggiornare

Tuttavia in C # 4 è implementato in modo diverso:

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

Grazie a CodeInChaos per commenti e collegamenti


In C # 4 l'istruzione lock viene implementata in modo diverso. blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
CodesInChaos

14

Monitorè più flessibile. Il mio caso d'uso preferito dell'utilizzo del monitor è quando non vuoi aspettare il tuo turno e saltare semplicemente :

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

6

Come altri hanno già detto, lockè "equivalente" a

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

Ma solo per curiosità, lockconserverà il primo riferimento che gli passi e non lo getterà se lo cambi. So che non è consigliabile cambiare l'oggetto bloccato e tu non vuoi farlo.

Ma ancora una volta, per la scienza, funziona bene:

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());

... E questo non:

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());

Errore:

Si è verificata un'eccezione di tipo "System.Threading.SynchronizationLockException" in 70783sTUDIES.exe ma non è stata gestita nel codice utente

Informazioni aggiuntive: il metodo di sincronizzazione degli oggetti è stato chiamato da un blocco di codice non sincronizzato.

Questo perchè Monitor.Exit(lockObject);agirà su lockObjectquale è cambiato perchè stringssono immutabili, quindi lo stai chiamando da un blocco di codice non sincronizzato .. ma comunque. Questo è solo un fatto divertente.


"Questo perché Monitor.Exit (lockObject); agirà su lockObject". Quindi il blocco non fa nulla con l'oggetto? Come funziona la serratura?
Yugo Amaryl

@ YugoAmaryl, suppongo sia perché l'istruzione lock tiene a mente il primo riferimento passato e poi lo usa invece di usare il riferimento modificato, come:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.

3

Entrambi sono la stessa cosa. lock è una parola chiave c sharp e usa la classe Monitor.

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


3
Guarda msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx "In effetti, la parola chiave lock è implementata con la classe Monitor. Ad esempio"
RobertoBr

1
l'implementazione sottostante di lock usa Monitor ma non sono la stessa cosa, considera i metodi forniti da monitor che non esistono per lock e il modo in cui puoi bloccare e sbloccare in blocchi di codice separati.
eran otzap

3

Il blocco e il comportamento di base del monitor (invio + uscita) è più o meno lo stesso, ma il monitor ha più opzioni che ti consentono più possibilità di sincronizzazione.

Il lucchetto è una scorciatoia ed è l'opzione per l'utilizzo di base.

Se hai bisogno di più controllo, il monitor è l'opzione migliore. Puoi usare Wait, TryEnter e Pulse, per usi avanzati (come barriere, semafori e così via).


1

La parola chiave Lock Lock garantisce che un thread stia eseguendo un pezzo di codice alla volta.

lock (lockObject)

        {
        //   Body
        }

La parola chiave lock contrassegna un blocco di istruzioni come sezione critica ottenendo il blocco di mutua esclusione per un dato oggetto, eseguendo un'istruzione e quindi rilasciando il blocco

Se un altro thread tenta di inserire un codice bloccato, attenderà, si bloccherà, fino al rilascio dell'oggetto.

Monitor Il Monitor è una classe statica e appartiene allo spazio dei nomi System.Threading.

Fornisce un blocco esclusivo sull'oggetto in modo che un solo thread possa entrare nella sezione critica in un dato momento.

Differenza tra monitoraggio e blocco in C #

Il lucchetto è la scorciatoia per Monitor.Entra con try e infine. Blocca le maniglie prova e infine blocca internamente Blocca = Controlla + prova finalmente.

Se si desidera un maggiore controllo per implementare soluzioni avanzate utilizzando multithreading TryEnter() Wait(), Pulse()ePulseAll() metodi, allora la classe Monitor è la vostra opzione.

C # Monitor.wait(): un thread attende la notifica di altri thread.

Monitor.pulse(): Un thread notifica a un altro thread.

Monitor.pulseAll(): Un thread notifica tutti gli altri thread all'interno di un processo


0

Oltre a tutte le spiegazioni precedenti, lock è un'istruzione C # mentre Monitor è una classe di .NET situata nello spazio dei nomi System.Threading.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.