2009-04-29 38 views
112

Por favor explique desde las perspectivas de Linux, Windows?¿Cuál es la diferencia entre mutex y sección crítica?

Estoy programando en C#, estos dos términos marcarían la diferencia. Por favor, puesto que todo lo que pueda, con ejemplos y tal ....

Gracias

+9

¿Por qué en la tierra está etiquetado 'language-agnostic' * y *' C# '? – Puppy

+4

Buena lectura para la persona que comentó y, posiblemente, las 5 personas que votaron a favor si tenían suficientes representantes: http://blogs.msdn.com/b/oldnewthing/archive/2012/10/17/10360184.aspx –

+0

Awesome link @ BrianR.Bondy gracias. – Wodzu

Respuesta

194

Para Windows, las secciones críticas son más livianas que las mutex.

Los mutexes se pueden compartir entre procesos, pero siempre dan como resultado una llamada al kernel que tiene una sobrecarga.

Las secciones críticas solo se pueden usar en un proceso, pero tienen la ventaja de que solo cambian al modo kernel en caso de desacuerdo: las adquisiciones imprevistas, que deberían ser el caso común, son increíblemente rápidas. En el caso de contención, ingresan al kernel para esperar en 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 para 1,000,000 adquisiciones y liberaciones no aprobadas, un mutex toma más de un segundo. Una sección crítica requiere ~ 50 ms para 1,000,000 adquiere.

Aquí está el código de prueba, ejecuté esto y obtuve resultados similares si mutex es el primero o el segundo, por lo que no estamos viendo ningún otro efecto.

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

me pega - tal vez deberías publicar tu código. Yo te voté por uno si te hace sentir mejor –

+1

Bien hecho. Upvoted. – ApplePieIsGood

+1

No estoy seguro si esto se relaciona o no (ya que no he compilado y probado su código), pero he encontrado que al llamar a WaitForSingleObject con INFINITE se obtiene un rendimiento bajo. Pasarle un valor de tiempo de espera de 1 y luego hacer un bucle mientras verifica su devolución ha marcado una gran diferencia en el rendimiento de algunos de mis códigos. Esto es principalmente en el contexto de esperar un proceso externo, sin embargo ... No es un mutex. YMMV. Me interesaría ver cómo se comporta mutex con esa modificación. La diferencia de tiempo resultante de esta prueba parece más grande de lo que debería esperarse. –

6

En Windows, una sección crítica es local para su proceso. Un mutex se puede compartir/acceder a través de procesos. Básicamente, las secciones críticas son mucho más baratas. No puedo comentar sobre Linux específicamente, pero en algunos sistemas son solo alias para la misma cosa.

12

Un mutex es un objeto que puede adquirir un hilo, lo que impide que otros hilos lo adquieran. Es consultivo, no obligatorio; un hilo puede usar el recurso que mutex representa sin adquirirlo.

Una sección crítica es una longitud de código garantizada por el sistema operativo para que no se interrumpa. En pseudo-código, que sería como:

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

¿Soy incorrecto? Le agradecería si los votantes de abajo comentaran con una razón. – Zifre

+0

+1 porque el voto negativo me confunde. : p Diría que esto es más correcto que las afirmaciones que sugieren que Mutex y Critical Section son dos mecanismos diferentes para multihilo. La sección crítica es cualquier sección de código a la que se debe acceder solo por un hilo. Usar mutexes es una forma de implementar secciones críticas. –

+1

Creo que el póster hablaba de primitivas de sincronización del modo de usuario, como un objeto de sección Crítica de win32, que solo proporciona una exclusión mutua. No sé acerca de Linux, pero el kernel de Windows tiene regiones críticas que se comportan como usted describe: no interrumpible. – Michael

17

sección crítica y objeto mutex no son específico del sistema operativo, sus conceptos de múltiples hilos/multiprocesamiento.

sección crítica es una pieza de código que sólo se debe ejecutar por él mismo en un momento dado (por ejemplo, hay 5 hilos se ejecutan simultáneamente y una función llamada "critical_section_function" que actualiza una matriz que ... no quieren los 5 hilos de la actualización de la matriz a la vez. Así que cuando se ejecuta el programa critical_section_function(), ninguno de los otros hilos debe dirigir su critical_section_function.

mutex * objeto mutex es una forma de implementar la crítica código de sección (piénselo como un token ... el hilo debe tener posesión de él para ejecutar el critical_section_code)

+1

Además, los mutex pueden compartirse entre procesos. – configurator

18

Además de las otras respuestas, los siguientes datos son específicos de las secciones críticas en las ventanas:

  • en ausencia de contención, la adquisición de una sección crítica es tan simple como una operación InterlockedCompareExchange
  • la estructura de sección crítica tiene espacio para un mutex. Inicialmente no está asignado
  • si hay conflicto entre hilos para una sección crítica, el mutex se asignará y se usará. El rendimiento de la sección crítica se degradará a la del mutex
  • si prevé una alta contención, puede asignar la sección crítica que especifica un recuento de vueltas.
  • si hay contención en una sección crítica con un recuento de vueltas, el hilo que intenta adquirir la sección crítica girará (ocupado-espera) para esos muchos ciclos de procesador. Esto puede dar como resultado un mejor rendimiento que dormir, ya que el número de ciclos para realizar un cambio de contexto a otro subproceso puede ser mucho mayor que el número de ciclos tomados por el subproceso propietario para liberar el mutex
  • si el recuento de vueltas expira, el mutex se asignará
  • cuando el hilo poseer libera la sección crítica, se requiere para comprobar si se asigna el mutex, si es a continuación, se establece el mutex para liberar un hilo de espera

En Linux, Creo que tienen un "bloqueo de giro" que tiene un propósito similar a la sección crítica con un recuento de vueltas.

+0

Desafortunadamente, una sección crítica de la ventana implica realizar una operación CAS * en el modo kernel *, que es enormemente más costoso que la operación real entrelazada. Además, las secciones críticas de Windows pueden tener recuentos de vueltas asociadas a ellas. – Promit

+1

Eso definitivamente no es cierto. CAS se puede hacer con cmpxchg en modo de usuario. – Michael

+0

Pensé que el conteo de centrifugado predeterminado era cero si llamabas a InitializeCriticalSection; debes llamar a InitializeCriticalSectionAndSpinCount si quieres aplicar un recuento de vueltas. Tienes una referencia para eso? –

71

Desde una perspectiva teórica, un critical section es un fragmento de código que no debe ser ejecutado por múltiples hilos a la vez porque el código accede a los recursos compartidos.

A mutex es un algoritmo (y algunas veces el nombre de una estructura de datos) que se usa para proteger secciones críticas.

Semaphores y Monitors son implementaciones comunes de un mutex.

En la práctica, hay muchas implementaciones mutex disponibles en Windows. Principalmente difieren 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 - Using concurrency for scalability para obtener un diagrama de los costos de diferentes implementaciones de mutex.

primitivas de sincronización disponibles.

La declaración lock(object) es implemen usando un Monitor - vea MSDN para la referencia.

En los últimos años se realiza mucha investigación en non-blocking synchronization. El objetivo es implementar algoritmos en una forma libre de bloqueo o sin esperar. En dichos algoritmos, un proceso ayuda a otros procesos a finalizar su trabajo para que el proceso finalmente pueda finalizar su trabajo. En consecuencia, un proceso puede finalizar su trabajo incluso cuando otros procesos que intentaron realizar algún trabajo se cuelgan. Usinig locks, no liberarían sus bloqueos y evitarían que otros procesos continúen.

+0

Al ver la respuesta aceptada, estaba pensando que tal vez recordaba el concepto de secciones críticas incorrecto, hasta que vi que ** Theoretical Perspective ** escribiste. :) –

+1

_Practical_ lock free programming es como Shangri La, excepto que existe. El [paper] de Keir Fraser (http://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf) (PDF) explora esto bastante interesante (desde 2004). Y todavía estamos luchando con eso en 2012. Apestamos. –

+0

Esta respuesta es realmente buena. Me gusta. –

5

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 de 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 

Considerando que el mutex son los 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 a nt! NtCreateMutant en kernel.

+0

¿Qué sucede cuando un programa que inicializa y utiliza un objeto Mutex se bloquea? ¿El objeto Mutex se desasigna automáticamente? No, diría. ¿Derecha? – Ankur

+4

Los objetos del núcleo tienen un recuento de referencias. Cerrar un identificador a un objeto disminuye el recuento de referencia y cuando llega a 0, el objeto se libera. Cuando un proceso falla, todos sus identificadores se cierran automáticamente, por lo que una exclusión mutua que solo ese proceso tiene un identificador se desasignaría automáticamente. – Michael

11

El Windows 'rápido' igual de selección crítica en Linux sería un futex, que significa espacio de usuario rápido mutex. La diferencia entre un futex y un mutex es que con un futex, el kernel solo se involucra cuando se requiere arbitraje, por lo que se ahorra la sobrecarga de hablar con el kernel cada vez que se modifica el contador atómico. Un futex también se puede compartir entre los procesos, usando los medios que se emplearían para compartir un mutex.

Desafortunadamente, futexes puede ser very tricky to implement (PDF).

Más allá de eso, es prácticamente el mismo en ambas plataformas. Está realizando actualizaciones atómicas, controladas por tokens a una estructura compartida de una manera que (afortunadamente) no causa inanición. Lo que queda es simplemente el método para lograr eso.

0

Gran respuesta de Michael. He agregado una tercera prueba para la clase mutex presentada en C++ 11. El resultado es algo interesante, y todavía es compatible con su aprobación original de objetos CRITICAL_SECTION para procesos individuales.

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 relación de tiempos para las dos últimas es más o menos comparable a Michael, pero mi máquina es, al menos, cuatro años menor que él, por lo que puede ver la evidencia de mayor velocidad entre 2009 y 2013, cuando salió el XPS-8700). La nueva clase mutex es dos veces más rápida que el mutex de Windows, pero aún menos de una décima parte de la velocidad del objeto Windows CRITICAL_SECTION. Tenga en cuenta que solo probé el mutex no recursivo. Los objetos CRITICAL_SECTION son recursivos (un hilo puede ingresarlos repetidamente, siempre que salga el mismo número de veces).

Cuestiones relacionadas