¿Por favor explique desde las perspectivas de Linux y Windows?
Estoy programando en C #, ¿marcarían la diferencia estos dos términos? Por favor, publique todo lo que pueda, con ejemplos y tal ...
Gracias
¿Por favor explique desde las perspectivas de Linux y Windows?
Estoy programando en C #, ¿marcarían la diferencia estos dos términos? Por favor, publique todo lo que pueda, con ejemplos y tal ...
Gracias
Respuestas:
Para Windows, las secciones críticas son más livianas que las mutexes.
Los mutexes se pueden compartir entre procesos, pero siempre resultan en una llamada del sistema al kernel que tiene algo de sobrecarga.
Las secciones críticas solo se pueden usar dentro de un proceso, pero tienen la ventaja de que solo cambian al modo kernel en el caso de contención: las adquisiciones no controladas, que deberían ser el caso común, son increíblemente rápidas. En el caso de contención, ingresan al kernel para esperar alguna primitiva de sincronización (como un evento o semáforo).
Escribí una aplicación de muestra rápida que compara el tiempo entre los dos. En mi sistema por 1,000,000 de adquisiciones y lanzamientos sin supervisión, un mutex toma más de un segundo. Una sección crítica toma ~ 50 ms por 1,000,000 de adquisiciones.
Aquí está el código de prueba, ejecuté esto y obtuve resultados similares si mutex es primero o segundo, por lo que no vemos otros efectos.
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);
Desde una perspectiva teórica, una sección crítica es un código que no debe ser ejecutado por varios subprocesos a la vez porque el código accede a recursos compartidos.
Un mutex es un algoritmo (y, a veces, el nombre de una estructura de datos) que se utiliza para proteger secciones críticas.
Los semáforos y monitores son implementaciones comunes de un mutex.
En la práctica, hay muchas implementaciones de mutex disponibles en Windows. Difieren principalmente como consecuencia de su implementación por su nivel de bloqueo, sus alcances, sus costos y su desempeño bajo diferentes niveles de contención. Consulte CLR Inside Out: uso de la concurrencia para la escalabilidad para obtener un gráfico de los costos de las diferentes implementaciones de mutex.
Primitivas de sincronización disponibles.
La lock(object)
declaración se implementa usando un Monitor
- vea MSDN para referencia.
En los últimos años se ha investigado mucho sobre la sincronización sin bloqueo . El objetivo es implementar algoritmos de manera sin bloqueo o sin espera. En tales algoritmos, un proceso ayuda a otros procesos a terminar su trabajo para que el proceso finalmente pueda terminar su trabajo. En consecuencia, un proceso puede terminar su trabajo incluso cuando otros procesos, que intentaron realizar algún trabajo, se bloquean. Usando bloqueos, no liberarían sus bloqueos y evitarían que otros procesos continúen.
Además de las otras respuestas, los siguientes detalles son específicos de las secciones críticas en Windows:
InterlockedCompareExchange
operaciónEn Linux, creo que tienen un "bloqueo de giro" que sirve para un propósito similar a la sección crítica con un conteo de giro.
Critical Section y Mutex no son específicos del sistema operativo, sus conceptos de multiprocesamiento / multiprocesamiento.
Sección crítica Es un fragmento de código que solo debe ejecutarse por sí mismo en un momento dado (por ejemplo, hay 5 subprocesos ejecutándose simultáneamente y una función llamada "critical_section_function" que actualiza una matriz ... no desea los 5 subprocesos actualizar la matriz de una vez. Entonces, cuando el programa está ejecutando critical_section_function (), ninguno de los otros subprocesos debe ejecutar su critical_section_function.
mutex * Mutex es una forma de implementar el código de sección crítica (piense en él como un token ... el hilo debe tener posesión para ejecutar el código_sección_crítica)
Un mutex es un objeto que un hilo puede adquirir, evitando que otros hilos lo adquieran. Es consultivo, no obligatorio; un hilo puede usar el recurso que representa el mutex sin adquirirlo.
Una sección crítica es una longitud de código que el sistema operativo garantiza que no se interrumpirá. En pseudocódigo, sería como:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
El "rápido" de Windows de selección crítica en Linux sería un futex , que significa mutex de espacio de usuario rápido. La diferencia entre un futex y un mutex es que con un futex, el núcleo solo se involucra cuando se requiere arbitraje, por lo que ahorra la sobrecarga de hablar con el núcleo cada vez que se modifica el contador atómico. Eso ... puede ahorrar una cantidad significativa de tiempo negociando bloqueos en algunas aplicaciones.
Un futex también se puede compartir entre procesos, utilizando los medios que emplearía para compartir un mutex.
Desafortunadamente, los futexes pueden ser muy difíciles de implementar (PDF). (Actualización de 2018, no dan tanto miedo como en 2009).
Más allá de eso, es más o menos lo mismo en ambas plataformas. Está realizando actualizaciones atómicas impulsadas por tokens a una estructura compartida de una manera que (con suerte) no causa inanición. Lo que queda es simplemente el método para lograrlo.
Solo para agregar mis 2 centavos, las secciones críticas se definen como una estructura y las operaciones en ellas se realizan en el contexto del modo de usuario.
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
Mientras que mutex son objetos del núcleo (ExMutantObjectType) creados en el directorio de objetos de Windows. Las operaciones Mutex se implementan principalmente en modo kernel. Por ejemplo, al crear un Mutex, terminas llamando nt! NtCreateMutant en el núcleo.
Gran respuesta de Michael. He agregado una tercera prueba para la clase mutex introducida en C ++ 11. El resultado es algo interesante y aún respalda su respaldo original de objetos CRITICAL_SECTION para procesos únicos.
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);
Mis resultados fueron 217, 473 y 19 (tenga en cuenta que mi proporción de veces para los dos últimos es aproximadamente comparable a la de Michael, pero mi máquina es al menos cuatro años menor que la suya, por lo que puede ver evidencia de una mayor velocidad entre 2009 y 2013 , cuando salió el XPS-8700). La nueva clase mutex es dos veces más rápida que la mutex de Windows, pero aún menos de una décima parte de la velocidad del objeto CRITICAL_SECTION de Windows. Tenga en cuenta que solo probé el mutex no recursivo. Los objetos CRITICAL_SECTION son recursivos (un hilo puede ingresarlos repetidamente, siempre que salga la misma cantidad de veces).
Las funciones de CA se denominan reentrantes si solo utiliza sus parámetros reales.
Las funciones reentrantes pueden ser llamadas por múltiples hilos al mismo tiempo.
Ejemplo de función reentrante:
int reentrant_function (int a, int b)
{
int c;
c = a + b;
return c;
}
Ejemplo de función no reentrante:
int result;
void non_reentrant_function (int a, int b)
{
int c;
c = a + b;
result = c;
}
La biblioteca estándar C strtok () no es reentrante y no puede ser utilizada por 2 o más hilos al mismo tiempo.
Algunos SDK de plataforma vienen con la versión reentrante de strtok () llamada strtok_r ();
Enrico Migliore