Quali sono le differenze tra le varie opzioni di sincronizzazione del threading in C #?


Risposte:


135

Ottima domanda Forse sbaglio .. Fammi provare .. Revisione n. 2 della mia risposta orig .. con un po 'più di comprensione. Grazie per avermi fatto leggere :)

serratura (obj)

  • è un costrutto CLR che per la sincronizzazione dei thread (all'interno dell'oggetto?). Assicura che solo un thread possa diventare proprietario del blocco dell'oggetto e immettere il blocco di codice bloccato. Gli altri thread devono attendere che l'attuale proprietario abbandoni il blocco uscendo dal blocco di codice. Inoltre, si consiglia di bloccare un oggetto membro privato della propria classe.

monitor

  • lock (obj) è implementato internamente usando un Monitor. Dovresti preferire il blocco (obj) perché ti impedisce di perdere tempo come dimenticare la procedura di pulizia. E 'a prova di idiota il costrutto Monitor, se vuoi.
    L'uso di Monitor è generalmente preferito ai mutex, perché i monitor sono stati progettati specificamente per .NET Framework e quindi fanno un uso migliore delle risorse.

L'uso di un blocco o di un monitor è utile per impedire l'esecuzione simultanea di blocchi di codice sensibili al thread, ma questi costrutti non consentono a un thread di comunicare un evento a un altro. Ciò richiede eventi di sincronizzazione , che sono oggetti con uno dei due stati, segnalati e non segnalati, che possono essere utilizzati per attivare e sospendere i thread. Mutex, Semaphores sono concetti a livello di sistema operativo. ad esempio con un mutex denominato è possibile sincronizzare più ex (gestiti) (assicurando che sul computer sia in esecuzione solo un'istanza dell'applicazione).

mutex:

  • A differenza dei monitor, tuttavia, un mutex può essere utilizzato per sincronizzare i thread tra i processi. Se utilizzato per la sincronizzazione tra processi, un mutex viene chiamato mutex denominato perché deve essere utilizzato in un'altra applicazione e pertanto non può essere condiviso mediante una variabile globale o statica. Deve essere assegnato un nome in modo che entrambe le applicazioni possano accedere allo stesso oggetto mutex. Al contrario, la classe Mutex è un wrapper per un costrutto Win32. Sebbene sia più potente di un monitor, un mutex richiede transizioni di interoperabilità più costose dal punto di vista computazionale di quelle richieste dalla classe Monitor.

Semafori (feriti al cervello).

  • Utilizzare la classe Semaphore per controllare l'accesso a un pool di risorse. I thread entrano nel semaforo chiamando il metodo WaitOne, che è ereditato dalla classe WaitHandle, e rilasciano il semaforo chiamando il metodo Release. Il conteggio di un semaforo viene ridotto ogni volta che un thread entra nel semaforo e viene incrementato quando un thread rilascia il semaforo. Quando il conteggio è zero, le richieste successive si bloccano fino a quando altri thread rilasciano il semaforo. Quando tutti i thread hanno rilasciato il semaforo, il conteggio è al valore massimo specificato al momento della creazione del semaforo. Un thread può inserire più volte il semaforo ... La classe Semaphore non impone l'identità del thread su WaitOne o Release .. la responsabilità dei programmatori di non confondere. I semafori sono di due tipi: semafori locali e denominatisemafori di sistema. Se si crea un oggetto Semaphore utilizzando un costruttore che accetta un nome, è associato a un semaforo del sistema operativo con quel nome. I semafori del sistema con nome sono visibili in tutto il sistema operativo e possono essere utilizzati per sincronizzare le attività dei processi. Un semaforo locale esiste solo all'interno del tuo processo. Può essere utilizzato da qualsiasi thread nel processo che ha un riferimento all'oggetto Semaphore locale. Ogni oggetto Semaphore è un semaforo locale separato.

LA PAGINA DA LEGGERE - Thread Synchronization (C #)


18
Lei sostiene che Monitornon consente la comunicazione è errata; puoi ancora Pulseecc con unMonitor
Marc Gravell

3
Controlla una descrizione alternativa di Semaphores - stackoverflow.com/a/40473/968003 . Pensa ai semafori come buttafuori in una discoteca. Ci sono un numero dedicato di persone che sono ammesse contemporaneamente nel club. Se il club è pieno, nessuno è autorizzato ad entrare, ma non appena una persona lascia un'altra persona può entrare.
Alex Klaus,

31

Ri "Utilizzo di altre classi di sincronizzazione .Net" - alcune delle altre che dovresti conoscere:

Esistono anche altri costrutti di blocco (bassi costi generali) in CCR / TPL ( Parallel Extensions CTP) - ma IIRC, questi saranno resi disponibili in .NET 4.0


Quindi, se voglio una semplice comunicazione del segnale (ad esempio il completamento di un'operazione asincrona), dovrei Monitor.Pulse? o utilizzare SemaphoreSlim o TaskCompletionSource?
Vivek,

Utilizzare TaskCompletionSource per l'operazione asincrona. Fondamentalmente, smetti di pensare alle discussioni e inizia a pensare alle attività (unità di lavoro). I thread sono dettagli di implementazione e non rilevanti. Restituendo un TCS, è possibile restituire risultati, errori o gestire la cancellazione ed è facilmente componibile con altre operazioni asincrone (come async wait o ContinueWith).
Simon Gillbee,

14

Come affermato in ECMA, e come si può osservare dai metodi Reflected, l'istruzione lock è sostanzialmente equivalente a

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
   
}
finally {
   System.Threading.Monitor.Exit(obj);
}

Dall'esempio di cui sopra vediamo che i monitor possono bloccare gli oggetti.

I mutexe sono utili quando è necessaria la sincronizzazione tra processi poiché possono bloccare un identificatore di stringa. Lo stesso identificatore di stringa può essere utilizzato da processi diversi per acquisire il blocco.

I semafori sono come i mutex sugli steroidi, consentono l'accesso simultaneo fornendo un conteggio massimo di accesso simultaneo '. Una volta raggiunto il limite, il semaforo inizia a bloccare qualsiasi ulteriore accesso alla risorsa fino a quando uno dei chiamanti rilascia il semaforo.


5
Questo zucchero sintattico è stato leggermente modificato in C # 4 Dai
Peter Gfader,

14

Ho fatto le lezioni e il supporto CLR per il threading in DotGNU e ho alcuni pensieri ...

A meno che non siano necessari blocchi incrociati, è sempre consigliabile evitare l'uso di Mutex e semafori. Queste classi in .NET sono wrapper attorno a Win32 Mutex e Semaphores e sono piuttosto pesanti (richiedono un cambio di contesto nel kernel che è costoso, specialmente se il tuo blocco non è in conflitto).

Come altri sono menzionati, l'istruzione lock C # è una magia del compilatore per Monitor.Enter e Monitor.Exit (esistente in un tentativo / finalmente).

I monitor hanno un meccanismo di segnale / attesa semplice ma potente che i mutex non hanno tramite i metodi Monitor.Pulse / Monitor.Wait. L'equivalente Win32 sarebbe oggetti evento tramite CreateEvent che in realtà esistono anche in .NET come WaitHandles. Il modello Pulse / Wait è simile a pthread_signal e pthread_wait di Unix ma sono più veloci perché possono essere operazioni interamente in modalità utente nel caso non conteso.

Monitor.Pulse / Wait è semplice da usare. In un thread, blocciamo un oggetto, controlliamo un flag / stato / proprietà e, se non è quello che ci aspettiamo, chiamiamo Monitor.Wait che rilascerà il blocco e attenderà l'invio di un impulso. Quando l'attesa ritorna, torniamo indietro e controlliamo di nuovo la bandiera / stato / proprietà. Nell'altro thread, blocchiamo l'oggetto ogni volta che cambiamo flag / state / property e quindi chiamiamo PulseAll per riattivare eventuali thread in ascolto.

Spesso vogliamo che le nostre classi siano thread-safe, quindi inseriamo i blocchi nel nostro codice. Tuttavia, capita spesso che la nostra classe sia utilizzata da un solo thread. Ciò significa che i blocchi rallentano inutilmente il nostro codice ... è qui che ottimizzazioni intelligenti nel CLR possono aiutare a migliorare le prestazioni.

Non sono sicuro dell'implementazione dei blocchi da parte di Microsoft, ma in DotGNU e Mono, un flag di stato dei blocchi è memorizzato nell'intestazione di ogni oggetto. Ogni oggetto in .NET (e Java) può diventare un blocco, quindi ogni oggetto deve supportarlo nella loro intestazione. Nell'implementazione di DotGNU, esiste un flag che consente di utilizzare una tabella hash globale per ogni oggetto che viene utilizzato come blocco: ciò ha il vantaggio di eliminare un sovraccarico di 4 byte per ogni oggetto. Questo non è ottimo per la memoria (specialmente per i sistemi embedded che non sono fortemente sottoposti a thread) ma ha un impatto sulle prestazioni.

Sia Mono che DotGNU usano efficacemente i mutex per eseguire il blocco / attesa, ma usano operazioni di confronto e scambio in stile spinlock per eliminare la necessità di eseguire effettivamente un hard lock a meno che non sia realmente necessario:

Puoi vedere un esempio di come i monitor possono essere implementati qui:

http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup


9

Un ulteriore avvertimento per il blocco su qualsiasi Mutex condiviso identificato con un ID stringa è che verrà impostato automaticamente su un mutex "Local \" e non verrà condiviso tra le sessioni in un ambiente terminal server.

Prefisso il tuo identificatore di stringa con "Global \" per garantire che l'accesso alle risorse di sistema condivise sia adeguatamente controllato. Stavo solo incontrando un mucchio di problemi per sincronizzare le comunicazioni con un servizio in esecuzione con l'account SYSTEM prima di rendermene conto.


5

Proverei a evitare "lock ()", "Mutex" e "Monitor" se puoi ...

Dai un'occhiata al nuovo spazio dei nomi System.Collections.Concurrent in .NET 4
Ha alcune belle classi di raccolta thread-safe

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

ConcurrentDictionary rock! nessun blocco manuale più per me!


2
Evitare il blocco ma utilizzare Monitor? Perché?
mafu,

@mafutrct Perché devi occuparti della sincronizzazione da solo.
Peter Gfader,

Oh, ora capisco, intendevi evitare TUTTE le tre idee menzionate. Sembrava che avresti usato Monitor ma non usare lock / Mutex.
mafu,

Non usare mai System.Collections.Concurrent. Sono una delle principali fonti di condizioni di gara e bloccano anche il thread dei chiamanti.
Alexander Danilov,

-2

Nella maggior parte dei casi non è necessario utilizzare blocchi (= monitor) o mutex / semafori. Tutti bloccano il thread corrente.

E sicuramente non dovresti usare System.Collections.Concurrent classi: sono la principale fonte di condizioni di gara perché non supportano le transazioni tra più raccolte e bloccano anche il thread corrente.

Sorprendentemente .NET non ha meccanismi efficaci per la sincronizzazione.

Ho implementato la coda seriale da GCD ( Objc/Swiftworld) su C # - strumento di sincronizzazione molto leggero, non bloccante che utilizza il pool di thread, con test.

È il modo migliore per sincronizzare qualsiasi cosa nella maggior parte dei casi, dall'accesso al database (ciao sqlite) alla logica aziendale.

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.