¿Cuáles son las diferencias entre varias opciones de sincronización de subprocesos en C #?

164

¿Alguien puede explicar la diferencia entre:

  • bloqueo (algún objeto) {}
  • Usando Mutex
  • Usando el semáforo
  • Usando Monitor
  • Uso de otras clases de sincronización .Net

Simplemente no puedo entenderlo. ¿Me parece que los dos primeros son iguales?

usuario38834
fuente
Este enlace me ayudó mucho: albahari.com/threading
Raphael

Respuestas:

135

Gran pregunta Tal vez me equivoque ... Déjame intentarlo ... Revisión # 2 de mi respuesta original ... con un poco más de comprensión. Gracias por hacerme leer :)

cerradura (obj)

  • es una construcción CLR que para la sincronización de subprocesos (intra-objeto?). Garantiza que solo un subproceso pueda tomar posesión del bloqueo del objeto e ingresar el bloque de código bloqueado. Otros subprocesos deben esperar hasta que el propietario actual abandone el bloqueo al salir del bloque de código. También se recomienda que bloquee un objeto miembro privado de su clase.

Monitores

  • lock (obj) se implementa internamente utilizando un monitor. Debes preferir el bloqueo (obj) porque evita que hagas tonterías, como olvidar el procedimiento de limpieza. Es a prueba de idiotas la construcción del Monitor, por así decirlo.
    El uso de Monitor generalmente se prefiere sobre mutexes, porque los monitores fueron diseñados específicamente para .NET Framework y, por lo tanto, hacen un mejor uso de los recursos.

El uso de un bloqueo o monitor es útil para evitar la ejecución simultánea de bloques de código sensibles a hilos, pero estas construcciones no permiten que un hilo comunique un evento a otro. Esto requiere eventos de sincronización , que son objetos que tienen uno de dos estados, señalizados y no señalizados, que se pueden usar para activar y suspender subprocesos. Mutex, semáforos son conceptos a nivel del sistema operativo. por ejemplo, con un mutex con nombre que podría sincronizar a través de múltiples exes (administrados) (asegurando que solo una instancia de su aplicación se esté ejecutando en la máquina).

Mutex:

  • Sin embargo, a diferencia de los monitores, se puede usar un mutex para sincronizar hilos entre procesos. Cuando se usa para la sincronización entre procesos, un mutex se llama mutex con nombre porque se debe usar en otra aplicación y, por lo tanto, no se puede compartir por medio de una variable global o estática. Debe tener un nombre para que ambas aplicaciones puedan acceder al mismo objeto mutex. Por el contrario, la clase Mutex es un contenedor para una construcción Win32. Si bien es más potente que un monitor, un mutex requiere transiciones de interoperabilidad que son computacionalmente más caras que las requeridas por la clase Monitor.

Semáforos (me duelen el cerebro).

  • Use la clase Semaphore para controlar el acceso a un grupo de recursos. Los subprocesos ingresan al semáforo llamando al método WaitOne, que se hereda de la clase WaitHandle, y liberan el semáforo llamando al método Release. El recuento de un semáforo disminuye cada vez que un subproceso ingresa al semáforo, y se incrementa cuando un subproceso libera el semáforo. Cuando el recuento es cero, las solicitudes posteriores se bloquean hasta que otros subprocesos liberan el semáforo. Cuando todos los hilos han liberado el semáforo, el recuento se encuentra en el valor máximo especificado cuando se creó el semáforo. Un hilo puede ingresar al semáforo varias veces ... La clase Semaphore no impone la identidad del hilo en WaitOne o Release ... la responsabilidad de los programadores de no muckear. Los semáforos son de dos tipos: semáforos locales y nombradossemáforos del sistema. Si crea un objeto Semáforo utilizando un constructor que acepta un nombre, se asocia con un semáforo del sistema operativo de ese nombre. Los semáforos del sistema con nombre son visibles en todo el sistema operativo y se pueden usar para sincronizar las actividades de los procesos. Un semáforo local existe solo dentro de su proceso. Puede ser utilizado por cualquier subproceso en su proceso que tenga una referencia al objeto Semaphore local. Cada objeto de semáforo es un semáforo local separado.

LA PÁGINA A LEER - Sincronización de subprocesos (C #)

Gishu
fuente
18
Afirma que Monitorno permite la comunicación es incorrecta; todavía puedes, Pulseetc. con unMonitor
Marc Gravell
3
Consulte una descripción alternativa de semáforos: stackoverflow.com/a/40473/968003 . Piense en los semáforos como gorilas en un club nocturno. Hay un número dedicado de personas que están permitidas en el club a la vez. Si el club está lleno, nadie puede ingresar, pero tan pronto como una persona se vaya, otra persona puede ingresar.
Alex Klaus
31

Re "Uso de otras clases de sincronización .Net" - algunas de las otras que debe conocer:

También hay más construcciones de bloqueo (gastos generales bajos) en CCR / TPL (el CTP de extensiones paralelas ), pero IIRC, estarán disponibles en .NET 4.0

Marc Gravell
fuente
Entonces, si quiero una comunicación de señal simple (por ejemplo, la finalización de una operación asincrónica), debería monitorear. o usar SemaphoreSlim o TaskCompletionSource?
Vivek
Utilice TaskCompletionSource para la operación asincrónica. Básicamente, deja de pensar en hilos y comienza a pensar en tareas (unidades de trabajo). Los hilos son un detalle de implementación y no son relevantes. Al devolver un TCS, puede devolver resultados, errores o manejar la cancelación y es fácilmente componible con otra operación asincrónica (como async en espera o ContinueWith).
Simon Gillbee
14

Como se indica en ECMA, y como puede observar en los métodos Reflejados, la declaración de bloqueo es básicamente equivalente a

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

Del ejemplo antes mencionado vemos que los Monitores pueden bloquear objetos.

Los Mutexe son útiles cuando necesita sincronización entre procesos, ya que pueden bloquear un identificador de cadena. El mismo identificador de cadena puede ser utilizado por diferentes procesos para adquirir el bloqueo.

Los semáforos son como mutexes en los esteroides, permiten el acceso concurrente al proporcionar un conteo máximo de acceso concurrente '. Una vez que se alcanza el límite, el semáforo comienza a bloquear cualquier acceso adicional al recurso hasta que una de las personas que llama suelte el semáforo.

arul
fuente
55
Este azúcar sintáctico ha cambiado ligeramente en C # 4. Visite blogs.msdn.com/ericlippert/archive/2009/03/06/…
Peter Gfader
14

Hice las clases y el soporte de CLR para subprocesos en DotGNU y tengo algunos pensamientos ...

A menos que requiera bloqueos de proceso cruzado, siempre debe evitar usar Mutex y semáforos. Estas clases en .NET son envoltorios alrededor de Win32 Mutex y Semáforos y son bastante pesadas (requieren un cambio de contexto en el Kernel que es costoso, especialmente si su bloqueo no está bajo contención).

Como se menciona a otros, la declaración de bloqueo de C # es magia del compilador para Monitor.Enter y Monitor.Exit (existente dentro de un intento / finalmente).

Los monitores tienen un mecanismo de señal / espera simple pero potente que Mutexes no tiene a través de los métodos Monitor.Pulse / Monitor.Wait. El equivalente de Win32 serían objetos de evento a través de CreateEvent que en realidad también existen en .NET como WaitHandles. El modelo Pulse / Wait es similar a pthread_signal y pthread_wait de Unix, pero son más rápidos porque pueden ser operaciones completamente en modo de usuario en el caso no disputado.

Monitor.Pulse / Wait es fácil de usar. En un subproceso, bloqueamos un objeto, verificamos un indicador / estado / propiedad y, si no es lo que esperamos, llama a Monitor.Wait, que liberará el bloqueo y esperará hasta que se envíe un pulso. Cuando la espera regresa, retrocedemos y verificamos la bandera / estado / propiedad nuevamente. En el otro hilo, bloqueamos el objeto cada vez que cambiamos la bandera / estado / propiedad y luego llamamos a PulseAll para despertar cualquier hilo de escucha.

A menudo queremos que nuestras clases sean seguras para subprocesos, por lo que ponemos bloqueos en nuestro código. Sin embargo, a menudo ocurre que nuestra clase solo será utilizada por un hilo. Esto significa que los bloqueos ralentizan innecesariamente nuestro código ... aquí es donde las optimizaciones inteligentes en el CLR pueden ayudar a mejorar el rendimiento.

No estoy seguro acerca de la implementación de bloqueos de Microsoft, pero en DotGNU y Mono, un indicador de estado de bloqueo se almacena en el encabezado de cada objeto. Cada objeto en .NET (y Java) puede convertirse en un bloqueo, por lo que cada objeto debe admitir esto en su encabezado. En la implementación de DotGNU, hay un indicador que le permite usar una tabla hash global para cada objeto que se usa como bloqueo; esto tiene el beneficio de eliminar una sobrecarga de 4 bytes para cada objeto. Esto no es excelente para la memoria (especialmente para los sistemas integrados que no están muy enhebrados) pero tiene un impacto en el rendimiento.

Tanto Mono como DotGNU utilizan eficazmente mutexes para realizar bloqueos / espera, pero utilizan un estilo de comparación de cambio de spinlock para eliminar la necesidad de realizar bloqueos duros a menos que sea realmente necesario:

Puede ver un ejemplo de cómo se pueden implementar los monitores aquí:

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

tumtumtum
fuente
9

Una advertencia adicional para bloquear cualquier Mutex compartido que haya identificado con una ID de cadena es que, por defecto, será un mutex "Local \" y no se compartirá entre sesiones en un entorno de servidor de terminal.

Prefije su identificador de cadena con "Global \" para garantizar que el acceso a los recursos compartidos del sistema se controle adecuadamente. Me encontraba con un montón de problemas al sincronizar las comunicaciones con un servicio que se ejecutaba bajo la cuenta SYSTEM antes de darme cuenta de esto.

nvuono
fuente
5

Intentaría evitar "lock ()", "Mutex" y "Monitor" si puedes ...

Echa un vistazo al nuevo espacio de nombres System.Collections.Concurrent en .NET 4
Tiene algunas buenas clases de colección seguras para subprocesos

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

ConcurrentDictionary rocks! ¡ya no hay bloqueo manual para mí!

Peter Gfader
fuente
2
¿Evitar bloquear pero usar Monitor? ¿Por qué?
mafu
@mafutrct Porque debes ocuparte de la sincronización tú mismo.
Peter Gfader
Oh, ahora lo entiendo, querías evitar TODAS las tres ideas mencionadas. Parecía que usarías Monitor pero no usarías lock / Mutex.
mafu
Nunca use System.Collections.Concurrent. Son una fuente principal de condiciones de carrera y también bloquean el hilo de las personas que llaman.
Alexander Danilov
-2

En la mayoría de los casos no deberías usar bloqueos (= Monitores) o mutexes / semáforos. Todos bloquean el hilo actual.

Y definitivamente no deberías usar System.Collections.Concurrent clases: son la principal fuente de condiciones de carrera porque no admiten transacciones entre varias colecciones y también bloquean el hilo actual.

Sorprendentemente .NET no tiene mecanismos efectivos para la sincronización.

Implementé la cola en serie de GCD ( Objc/Swiftmundo) en C #, una herramienta de sincronización muy liviana, que no bloquea y que usa un grupo de subprocesos, con pruebas.

Es la mejor manera de sincronizar cualquier cosa en la mayoría de los casos, desde el acceso a la base de datos (hola sqlite) hasta la lógica empresarial.

Alexander Danilov
fuente