Per favore, spiega da Linux, le prospettive di Windows?
Sto programmando in C #, questi due termini farebbero la differenza. Pubblica il più possibile, con esempi e simili ....
Grazie
Per favore, spiega da Linux, le prospettive di Windows?
Sto programmando in C #, questi due termini farebbero la differenza. Pubblica il più possibile, con esempi e simili ....
Grazie
Risposte:
Per Windows, le sezioni critiche sono più leggere dei mutex.
I mutex possono essere condivisi tra i processi, ma comportano sempre una chiamata di sistema al kernel con un overhead.
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: le acquisizioni indesiderate, che dovrebbero 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 un'app di esempio rapida che confronta il tempo tra i due. Sul mio sistema per 1.000.000 di acquisizioni e rilasci non programmati, un mutex richiede oltre un secondo. Una sezione critica richiede ~ 50 ms per 1.000.000 di acquisizioni.
Ecco il codice del test, l'ho eseguito e ho ottenuto risultati simili se mutex è il primo o il 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);
Dal punto di vista teorico, una sezione critica è un pezzo di codice che non deve essere eseguito da più thread contemporaneamente perché il codice accede alle risorse condivise.
Un mutex è un algoritmo (e talvolta il nome di una struttura di dati) che viene utilizzato per proteggere sezioni critiche.
Semafori e monitor sono implementazioni comuni di un mutex.
In pratica ci sono molte implementazioni di mutex disponibili in Windows. Differiscono principalmente come conseguenza della loro implementazione per il loro livello di blocco, i loro scopi, i loro costi e le loro prestazioni in diversi livelli di contesa. Vedi CLR Inside Out - Utilizzo della concorrenza per la scalabilità per un grafico dei costi delle diverse implementazioni di mutex.
Primitive di sincronizzazione disponibili.
L' lock(object)
istruzione viene implementata usando a Monitor
- vedi MSDN per riferimento.
Negli ultimi anni sono state fatte molte ricerche sulla sincronizzazione non bloccante . L'obiettivo è quello di implementare algoritmi in modo lock-free o wait-free. In tali algoritmi un processo aiuta altri processi a finire il loro lavoro in modo che il processo possa finalmente finire il suo lavoro. Di conseguenza un processo può terminare il suo lavoro anche quando altri processi, che hanno tentato di eseguire qualche lavoro, si bloccano. Usando i blocchi, non rilasciano i blocchi e impediscono che altri processi continuino.
Oltre alle altre risposte, i seguenti dettagli sono specifici delle sezioni critiche su Windows:
InterlockedCompareExchange
un'operazioneIn Linux, penso che abbiano uno "spin lock" che ha uno scopo simile alla sezione critica con un conteggio degli spin.
Critical Section e Mutex non sono specifici del sistema operativo, i loro concetti di multithreading / multiprocessing.
Sezione critica È un pezzo di codice che deve essere eseguito da solo solo in un determinato momento (ad esempio, ci sono 5 thread in esecuzione contemporaneamente e una funzione chiamata "critical_section_function" che aggiorna un array ... non vuoi tutti i 5 thread aggiornando l'array in una sola volta, quindi quando il programma esegue critical_section_function (), nessuno degli altri thread deve eseguire la funzione critical_section_function.
mutex * Mutex è un modo per implementare il codice della sezione critica (pensalo come un token ... il thread deve essere in possesso di esso per eseguire il codice critico_section_)
Un mutex è un oggetto che un thread può acquisire, impedendo ad altri thread di acquisirlo. È consultivo, non obbligatorio; un thread può usare la risorsa rappresentata dal mutex senza acquisirla.
Una sezione critica è una lunghezza di codice che è garantita dal sistema operativo per non essere interrotta. In pseudo-codice, sarebbe come:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
L'equivalente "veloce" di Windows nella selezione critica in Linux sarebbe un futex , che significa mutex veloce per lo spazio utente. La differenza tra un futex e un mutex è che con un futex, il kernel viene coinvolto solo quando è richiesto l'arbitrato, quindi si risparmia il sovraccarico di parlare con il kernel ogni volta che si modifica il contatore atomico. Questo ... può far risparmiare un notevole periodo di tempo negoziando i blocchi in alcune applicazioni.
Un futex può anche essere condiviso tra i processi, usando i mezzi che impiegheresti per condividere un mutex.
Sfortunatamente, i futex possono essere molto difficili da implementare (PDF). (Aggiornamento del 2018, non sono così spaventosi come lo erano nel 2009).
Oltre a ciò, è praticamente lo stesso su entrambe le piattaforme. Stai apportando aggiornamenti atomici e basati su token a una struttura condivisa in modo che (si spera) non causi fame. Ciò che rimane è semplicemente il metodo per realizzarlo.
In Windows, una sezione critica è locale per il tuo processo. Un mutex può essere condiviso / accessibile attraverso i processi. Fondamentalmente, le sezioni critiche sono molto più economiche. Non posso commentare specificamente Linux, ma su alcuni sistemi sono solo alias per la stessa cosa.
Solo per aggiungere i miei 2 centesimi, le sezioni critiche sono definite come una struttura e le operazioni su di esse vengono eseguite nel contesto in 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
Considerando che mutex sono oggetti del kernel (ExMutantObjectType) creati nella directory degli oggetti di Windows. Le operazioni di Mutex sono per lo più implementate in modalità kernel. Ad esempio, quando si crea un Mutex, si finisce per chiamare nt! NtCreateMutant nel kernel.
Ottima risposta di Michael. Ho aggiunto un terzo test per la classe mutex introdotta in C ++ 11. Il risultato è alquanto interessante e supporta ancora la sua originale approvazione 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 (nota che il mio rapporto di volte negli ultimi due è approssimativamente paragonabile a quello di Michael, ma la mia macchina è almeno quattro anni più giovane della sua, quindi puoi vedere prove di una maggiore velocità tra il 2009 e il 2013 , quando è uscito l'XPS-8700). La nuova classe mutex è due volte più veloce del mutex di Windows, ma comunque meno di un decimo della velocità dell'oggetto CRITICAL_SECTION di Windows. Nota 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).
Le funzioni CA sono chiamate rientranti se usano solo i suoi parametri attuali.
Le funzioni rientranti possono essere richiamate da più thread contemporaneamente.
Esempio di funzione rientrante:
int reentrant_function (int a, int b)
{
int c;
c = a + b;
return c;
}
Esempio di funzione non rientrante:
int result;
void non_reentrant_function (int a, int b)
{
int c;
c = a + b;
result = c;
}
La libreria standard C strtok () non è rientrante e non può essere utilizzata da 2 o più thread contemporaneamente.
Alcune piattaforme SDK vengono fornite con la versione rientrante di strtok () chiamata strtok_r ();
Enrico Migliore