2011-08-01 16 views
6

specifica ECMA-335 indica:interblocco Monitor.Enter e Monitor.Exit blocchi

* acquisizione di un blocco (System.Threading.Monitor.Enter o immettendo un metodo sincronizzato) svolgeranno implicitamente una lettura volatili operazione, e rilasciando un blocco (System.Threading.Monitor.Exit o lasciando un metodo sincronizzato) eseguirà implicitamente un'operazione di scrittura volatile. (...)

Una lettura volatili ha acquisire semantica significa che la lettura viene garantito avvengano prima tutti i riferimenti a memoria che si verificano dopo l'istruzione di lettura della sequenza di istruzioni CIL. Una scrittura volatile ha una semantica di rilascio che significa che la scrittura è garantita per accadere dopo qualsiasi riferimento di memoria prima dell'istruzione di scrittura nella sequenza di istruzioni CIL. *

Ciò significa che i compilatori non possono spostare istruzioni fuori da Monitor.Enter/Monitor.Esci i blocchi, ma non è vietato spostare altre istruzioni nel blocco. Forse, anche un altro Monitor.Enter potrebbe essere spostato nel blocco (come può essere scambiato una scrittura volatile seguita da una lettura volatile). Quindi, potrebbe il seguente codice:

class SomeClass 
{ 
    object _locker1 = new object(); 
    object _locker2 = new object(); 

    public void A() 
    { 
     Monitor.Enter(_locker1); 
     //Do something 
     Monitor.Exit(_locker1); 
     Monitor.Enter(_locker2); 
     //Do something 
     Monitor.Exit(_locker2); 
    } 

    public void B() 
    { 
     Monitor.Enter(_locker2); 
     //Do something 
     Monitor.Exit(_locker2); 
     Monitor.Enter(_locker1); 
     //Do something 
     Monitor.Exit(_locker1); 
    } 
} 

, essere trasformato in un equivalente del followig:

class SomeClass 
{ 
    object _locker1 = new object(); 
    object _locker2 = new object(); 

    public void A() 
    { 
     Monitor.Enter(_locker1); 
     //Do something 
     Monitor.Enter(_locker2); 
     Monitor.Exit(_locker1); 
     //Do something 
     Monitor.Exit(_locker2); 
    } 

    public void B() 
    { 
     Monitor.Enter(_locker2); 
     //Do something 
     Monitor.Enter(_locker1); 
     Monitor.Exit(_locker2); 
     //Do something 
     Monitor.Exit(_locker1); 
    } 
} 

, che possano portare a situazioni di stallo? O mi sto perdendo qualcosa?

+0

Stai parlando di due cose molto diverse. Ciò che è rilevante è che né il compilatore né il jitter potranno mai riordinare le chiamate di metodo. –

+0

Hmm ... domanda interessante. Non sono sicuro di essere pronto a comprare il metodo di riordino del problema, eppure ancora. Intendo cosa succederebbe se l'ottimizzazione dell'innesto avvenga * prima * dell'ottimizzazione del sollevamento. C'è una clausola nelle specifiche che impedirebbe questo? –

risposta

2

Le specifiche ECMA-335 sono al o più debole di what the CLR (and every other implementation) uses.

Ricordo di aver letto (per la prima volta) il primo tentativo di Microsoft di eseguire il porting su IA-64, usando un modello di memoria più debole.Avevano così tanto del loro codice a seconda del linguaggio di blocco a doppio controllo (che è is broken sotto il modello di memoria più debole), che hanno appena implementato il modello più forte su quella piattaforma.

Joe Duffy ha a great post che riassume il modello di memoria CLR (attuale) per noi semplici mortali. C'è anche un link a un articolo di MSDN che spiega in modo più dettagliato come il CLR differisce da ECMA-335.

Non credo che sia un problema nella pratica; assumete semplicemente il modello di memoria CLR, poiché tutti gli altri lo fanno. Nessuno creerebbe un'implementazione debole a questo punto, dal momento che la maggior parte del codice si romperebbe semplicemente.

+0

Infatti, se questo non funzionasse ovunque, avrebbe un effetto disastroso. – XanderMK

1

Quando si utilizza lock o Monitor.Enter e Monitor.Exit questo è recinzioni piene, che significa che creerà una "barriera" in memoria Thread.MemoryBarrier() al beggening o il blocco "Monitor.Enter" e prima della fine della serratura "Monitor.Exit" . Quindi nessuna operazione si sposterà prima e dopo il blocco, ma si noti che le operazioni all'interno del blocco stesso possono essere scambiate da altre prospettive di thread, ma questo non è mai stato un problema poiché il blocco garantirà l'esclusiva reciproca, quindi solo un thread sarà eseguire il codice all'interno del blocco allo stesso tempo. Ad ogni modo il riordino comunque non avverrà in un singolo thread, cioè, quando il multithread inserisce la stessa regione di codice, possono vedere le istruzioni non nello stesso ordine.

Vi consiglio caldamente di leggere ulteriori informazioni su MemoryBarrier e sui recinti pieni e mezzi sull'articolo this.

Edit: Si noti che qui sto descrivendo il fatto che lock è pieno recinzione, ma non si parla di "dead lock" che siete a conoscenza di, lo scenario che descrivi non potrà mai si verifica, perché come @Hans detto che il riordino sarà mai si verifica per un metodo di chiamate, vale a dire:

Method1(); 
Method2(); 
Method3(); 

sarà sempre execute in sequenza, ma le istruzioni al loro interno possono riordinare, come quando multithread eseguire il codice che si trova all'interno Method1() ..

+2

Sì, molte fonti affermano che Monitor.Enter e Monitor.Exit sono full recinzioni di memoria, ma la specifica ECMA-335 stessa garantisce solo metà recinzioni all'inizio e alla fine. Tuttavia, si può ancora avere ragione sull'implementazione di Microsoft in quanto diventa più severa rispetto all'ECMA-335. Accetto che questa domanda riguardi più la teoria che la pratica, ma un'altra implementazione CLI potrebbe essere meno rigorosa, quindi la domanda rimane ancora aperta. – XanderMK