2009-04-29 6 views
112

Si prega di spiegare da Linux, prospettive di Windows?Qual è la differenza tra mutex e sezione critica?

Sto programmando in C#, questi due termini farebbero la differenza. Si prega di inviare il più possibile, con esempi e come ....

Grazie

+9

Perché sulla terra è codificato 'language-agnostic' * e *' C# '? – Puppy

+4

Buona lettura per la persona che ha commentato e possibilmente le 5 persone che hanno alzato l'audio se avevano abbastanza rep: http://blogs.msdn.com/b/oldnewthing/archive/2012/10/17/10360184.aspx –

+0

Link impressionante @ BrianR.Bondy grazie. – Wodzu

risposta

194

Per Windows, le sezioni critiche sono più leggere dei mutex.

I mutex possono essere condivisi tra processi, ma comportano sempre una chiamata di sistema al kernel che ha un sovraccarico.

Le sezioni critiche possono essere utilizzate solo all'interno di un processo, ma hanno il vantaggio di passare alla modalità kernel solo in caso di contesa: gli acquisti non finalizzati, che dovrebbe essere il caso comune, sono incredibilmente veloci. In caso di contesa, entrano nel kernel per attendere alcune primitive di sincronizzazione (come un evento o un semaforo).

Ho scritto una rapida app di esempio che confronta il tempo tra loro due. Sul mio sistema per 1.000.000 di acquisizioni e rilasci non convenzionali, un mutex occupa un secondo. Una sezione critica richiede ~ 50 ms per 1.000.000 di acquisizioni.

Ecco il codice di test, ho eseguito questo e ottenuto risultati simili se mutex è primo o secondo, quindi non vediamo altri effetti.

HANDLE mutex = CreateMutex(NULL, FALSE, NULL); 
CRITICAL_SECTION critSec; 
InitializeCriticalSection(&critSec); 

LARGE_INTEGER freq; 
QueryPerformanceFrequency(&freq); 
LARGE_INTEGER start, end; 

// Force code into memory, so we don't see any effects of paging. 
EnterCriticalSection(&critSec); 
LeaveCriticalSection(&critSec); 
QueryPerformanceCounter(&start); 
for (int i = 0; i < 1000000; i++) 
{ 
    EnterCriticalSection(&critSec); 
    LeaveCriticalSection(&critSec); 
} 

QueryPerformanceCounter(&end); 

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000/freq.QuadPart); 

// Force code into memory, so we don't see any effects of paging. 
WaitForSingleObject(mutex, INFINITE); 
ReleaseMutex(mutex); 

QueryPerformanceCounter(&start); 
for (int i = 0; i < 1000000; i++) 
{ 
    WaitForSingleObject(mutex, INFINITE); 
    ReleaseMutex(mutex); 
} 

QueryPerformanceCounter(&end); 

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000/freq.QuadPart); 

printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS); 
+0

mi batte - forse dovresti pubblicare il tuo codice. Ti ho votato uno se ti fa sentire meglio –

+1

Ben fatto. Upvoted. – ApplePieIsGood

+1

Non sono sicuro se questo si riferisce o meno (dal momento che non ho compilato e provato il tuo codice), ma ho trovato che chiamare WaitForSingleObject con INFINITE si traduce in scarse prestazioni. Passandogli un valore di timeout pari a 1, quindi eseguendo il ciclo durante il controllo del suo ritorno, hai fatto un'enorme differenza nelle prestazioni di alcuni dei miei codici. Questo è principalmente nel contesto dell'attesa di un handle di processo esterno, tuttavia ... Non è un mutex. YMMV. Sarei interessato a vedere come si comporta mutex con quella modifica. La differenza di tempo risultante da questo test sembra più grande di quanto ci si potrebbe aspettare. –

6

In Windows, una sezione critica è locale al vostro processo. Un mutex può essere condiviso/accessibile attraverso i processi. Fondamentalmente, le sezioni critiche sono molto più economiche. Non posso commentare specificamente su Linux, ma su alcuni sistemi sono solo alias per la stessa cosa.

12

Un mutex è un oggetto che un thread può acquisire, impedendo ad altri thread di acquisirlo. È consultivo, non obbligatorio; una discussione può usare la risorsa rappresentata dal mutex senza acquisirla.

Una sezione critica è una lunghezza di codice che è garantita dal fatto che il sistema operativo non deve essere interrotto. In pseudo-codice, sarebbe come:

StartCriticalSection(); 
    DoSomethingImportant(); 
    DoSomeOtherImportantThing(); 
EndCriticalSection(); 
+2

Sono errato? Lo apprezzerei se gli elettori commentassero con un motivo. – Zifre

+0

+1 perché il voto negativo mi confonde. : p Direi che è più corretto delle istruzioni che suggeriscono Mutex e Critical Section sono due meccanismi diversi per il multithreading. La sezione critica è una qualsiasi sezione di codice a cui si dovrebbe accedere solo da un thread. L'utilizzo di mutex è un modo per implementare sezioni critiche. –

+1

Penso che il poster stesse parlando delle primitive di sincronizzazione in modalità utente, come un oggetto della sezione critica win32, che fornisce solo l'esclusione reciproca. Non conosco Linux, ma il kernel di Windows ha regioni critiche che si comportano come te descrivono: non interrompibile. – Michael

17

sezione critica e Mutex non funzionano sistema specifico, i concetti di multithreading/multiprocessing.

sezione critica è un pezzo di codice che deve essere eseguito solo da esso auto in un dato momento (ad esempio, ci sono 5 thread in esecuzione contemporaneamente e una funzione chiamata "critical_section_function" che aggiorna una matrice ... voi non vogliono tutte e 5 le discussioni aggiornamento della matrice in una sola volta. Così, quando il programma è in esecuzione critical_section_function(), nessuno degli altri thread deve funzionare il loro critical_section_function.

mutex * mutex è un modo di attuare la critica codice di sezione (pensatelo come un token ... il thread deve esserne in possesso per eseguire il critical_section_code)

+1

Inoltre, i mutex possono essere condivisi tra i processi. – configurator

18

In aggiunta alle altre risposte, i seguenti dati sono specifici per sezioni critiche sulle finestre:

  • in assenza di contesa, acquisendo una sezione critica è semplice come un'operazione InterlockedCompareExchange
  • la struttura della sezione critica contiene spazio per un mutex. Inizialmente non è allocato
  • se esiste una contesa tra i thread per una sezione critica, il mutex verrà allocato e utilizzato. Le prestazioni della sezione critica si ridurranno a quella del mutex
  • se si prevede una contesa elevata, è possibile allocare la sezione critica specificando un conteggio degli spin.
  • se c'è una contesa su una sezione critica con un conteggio degli spin, il thread che tenta di acquisire la sezione critica ruoterà (busy-wait) per i molti cicli del processore. Ciò può comportare prestazioni migliori rispetto al sonno, poiché il numero di cicli per eseguire un cambio di contesto su un altro thread può essere molto più alto del numero di cicli eseguiti dal thread proprietario per rilasciare il mutex
  • se il conteggio degli spin scade, il mutex sarà assegnato
  • quando il filo possiede rilascia la sezione critica, è necessario controllare se il mutex è allocato, se è allora sarà impostato il mutex per rilasciare un thread in attesa

In linux, Penso che abbiano un "spin lock" che ha uno scopo simile alla sezione critica con un numero di giri.

+0

Sfortunatamente una sezione critica di Window richiede di eseguire un'operazione CAS * in modalità kernel *, che è molto più costosa dell'effettiva operazione interbloccata. Inoltre, le sezioni critiche di Windows possono avere associati i conteggi di spin. – Promit

+1

Ciò non è assolutamente vero. CAS può essere fatto con cmpxchg in modalità utente. – Michael

+0

Ho pensato che il numero di spin predefinito fosse zero se hai chiamato InitializeCriticalSection - devi chiamare InitializeCriticalSectionAndSpinCount se vuoi applicare un numero di spin. Hai un riferimento per questo? –

71

Da un punto di vista teorico, un critical section è un pezzo di codice che non deve essere eseguito da più thread contemporaneamente poiché il codice accede a risorse condivise.

A mutex A è un algoritmo (e talvolta il nome di una struttura dati) che viene utilizzato per proteggere le sezioni critiche.

Semaphores e Monitors sono implementazioni comuni di un mutex.

In pratica ci sono molte implementazioni mutex disponibili in Windows. Si differenziano principalmente come conseguenza della loro implementazione dal loro livello di blocco, i loro scopi, i loro costi e le loro prestazioni a diversi livelli di contesa. Vedere CLR Inside Out - Using concurrency for scalability per un grafico dei costi delle diverse implementazioni mutex.

Primitive di sincronizzazione disponibili.

La dichiarazione lock(object) è implemen ted utilizzando Monitor - vedere MSDN come riferimento.

Negli ultimi anni sono state effettuate molte ricerche su non-blocking synchronization. L'obiettivo è implementare algoritmi in modalità lock-free o wait-free. In tali algoritmi un processo aiuta altri processi a completare il loro lavoro in modo che il processo possa finalmente terminare il suo lavoro. Di conseguenza un processo può terminare il suo lavoro anche quando altri processi, che hanno cercato di eseguire un lavoro, si bloccano. Usando i blocchi, non rilasciano i blocchi e impediscono il proseguimento di altri processi.

+0

Vedendo la risposta accettata, stavo pensando che forse ho ricordato il concetto di sezioni critiche sbagliate, finché non ho visto quella ** Prospettiva teoretica ** che hai scritto. :) –

+1

_Practical_ lock programmazione gratuita è come Shangri La, tranne che esiste. Keir Fraser's [paper] (http://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf) (PDF) esplora questo aspetto piuttosto interessante (risalente al 2004). E lo stiamo ancora combattendo nel 2012. Facciamo schifo. –

+0

Questa risposta è davvero buona. Mi piace. –

5

Solo per aggiungere i miei 2 centesimi, le sezioni critiche sono definite come una struttura e le operazioni su di esse vengono eseguite in contesto modalità utente.

ntdll!_RTL_CRITICAL_SECTION 
    +0x000 DebugInfo  : Ptr32 _RTL_CRITICAL_SECTION_DEBUG 
    +0x004 LockCount  : Int4B 
    +0x008 RecursionCount : Int4B 
    +0x00c OwningThread  : Ptr32 Void 
    +0x010 LockSemaphore : Ptr32 Void 
    +0x014 SpinCount  : Uint4B 

Mentre i mutex sono oggetti kernel (ExMutantObjectType) creati nella directory degli oggetti di Windows. Le operazioni mutex sono per lo più implementate in modalità kernel. Ad esempio, quando si crea un Mutex, si finisce per chiamare nt! NtCreateMutant nel kernel.

+0

Cosa succede quando un programma che inizializza e utilizza un oggetto Mutex, si arresta in modo anomalo? L'oggetto Mutex viene automaticamente deallocato? No, direi. Destra? – Ankur

+4

Gli oggetti del kernel hanno un conteggio dei riferimenti. La chiusura di un handle su un oggetto diminuisce il conteggio dei riferimenti e quando raggiunge 0 l'oggetto viene liberato. Quando un processo si blocca, tutti i relativi handle vengono automaticamente chiusi, quindi un mutex a cui solo quel processo ha un handle verrà automaticamente rilasciato. – Michael

11

Windows 'veloce' uguale alla selezione critica in Linux sarebbe un futex, che sta per mutex dello spazio utente veloce. La differenza tra un futex e un mutex è che con un futex, il kernel viene coinvolto solo quando è richiesto l'arbitraggio, quindi si risparmia il sovraccarico di parlare con il kernel ogni volta che viene modificato il contatore atomico. Un futex può anche essere condiviso tra i processi, utilizzando i mezzi che utilizzeresti per condividere un mutex.

Sfortunatamente, i futex possono essere very tricky to implement (PDF).

Oltre a ciò, è praticamente uguale su entrambe le piattaforme. Stai facendo aggiornamenti atomici, basati su token a una struttura condivisa in un modo che (si spera) non causi fame. Ciò che rimane è semplicemente il metodo per realizzarlo.

0

Ottima risposta da parte di Michael. Ho aggiunto un terzo test per la classe mutex introdotta in C++ 11. Il risultato è alquanto interessante e supporta ancora la sua approvazione originale di oggetti CRITICAL_SECTION per singoli processi.

mutex m; 
HANDLE mutex = CreateMutex(NULL, FALSE, NULL); 
CRITICAL_SECTION critSec; 
InitializeCriticalSection(&critSec); 

LARGE_INTEGER freq; 
QueryPerformanceFrequency(&freq); 
LARGE_INTEGER start, end; 

// Force code into memory, so we don't see any effects of paging. 
EnterCriticalSection(&critSec); 
LeaveCriticalSection(&critSec); 
QueryPerformanceCounter(&start); 
for (int i = 0; i < 1000000; i++) 
{ 
    EnterCriticalSection(&critSec); 
    LeaveCriticalSection(&critSec); 
} 

QueryPerformanceCounter(&end); 

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000/freq.QuadPart); 

// Force code into memory, so we don't see any effects of paging. 
WaitForSingleObject(mutex, INFINITE); 
ReleaseMutex(mutex); 

QueryPerformanceCounter(&start); 
for (int i = 0; i < 1000000; i++) 
{ 
    WaitForSingleObject(mutex, INFINITE); 
    ReleaseMutex(mutex); 
} 

QueryPerformanceCounter(&end); 

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000/freq.QuadPart); 

// Force code into memory, so we don't see any effects of paging. 
m.lock(); 
m.unlock(); 

QueryPerformanceCounter(&start); 
for (int i = 0; i < 1000000; i++) 
{ 
    m.lock(); 
    m.unlock(); 
} 

QueryPerformanceCounter(&end); 

int totalTimeM = (int)((end.QuadPart - start.QuadPart) * 1000/freq.QuadPart); 


printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS); 

I miei risultati sono stati 217, 473, e 19 (si noti che il mio rapporto di volte per gli ultimi due è più o meno paragonabile a Michael, ma la mia macchina è di almeno quattro anni più giovane di suo, in modo da poter vedere la prova di maggiore velocità tra il 2009 e il 2013, quando uscì l'XPS-8700). La nuova classe mutex è due volte più veloce del mutex di Windows, ma ancora meno di un decimo della velocità dell'oggetto CRITICAL_SECTION di Windows. Si noti che ho testato solo il mutex non ricorsivo. Gli oggetti CRITICAL_SECTION sono ricorsivi (un thread può inserirli ripetutamente, purché lasci lo stesso numero di volte).