Share via


Información general sobre los primitivos de sincronización

.NET Framework proporciona un intervalo de primitivos de sincronización para controlar las interacciones de subprocesos y evitar las condiciones de carrera. Éstos se pueden dividir básicamente en tres categorías: operaciones de bloqueo, señalización e interbloqueo.

La definición de estas categorías no es clara ni nítida: algunos mecanismos de sincronización tienen características de varias categorías; los eventos que liberan un único subproceso a la vez actúan funcionalmente como bloqueos; la liberación de cualquier bloqueo se puede considerar como una señal y las operaciones de interbloqueo se pueden usar para construir bloqueos. Sin embargo, las categorías siguen siendo útiles.

Es importante recordar que la sincronización de subprocesos es cooperativa. Incluso si un subproceso omite un mecanismo de sincronización y tiene acceso directamente al recurso protegido, ese mecanismo de la sincronización no puede ser eficaz.

Esta información general contiene las siguientes secciones:

  • Bloqueo

  • Señalización

  • Tipos de sincronización ligeros

  • SpinWait

  • Operaciones de interbloqueo

Bloqueo

Los bloqueos proporcionan el control de un recurso a un subproceso cada vez, o a un número especificado de subprocesos. Un subproceso que solicita un bloqueo exclusivo cuando el bloqueo está en uso queda bloqueado hasta que el bloqueo está disponible.

Bloqueos exclusivos

La forma más sencilla de bloqueo es la instrucción lock de C# (SyncLock en Visual Basic), que controla el acceso a un bloque de código. Este tipo de bloque frecuentemente se suele denominar sección crítica. La instrucción lock se implementa utilizando los métodos Enter y Exit de la clase Monitor, y utiliza try…catch…finally para garantizar que se libera el bloqueo.

En general, utilizar la instrucción lock para proteger pequeños bloques de código, sin abarcar nunca más de un único método, es la mejor manera de usar la clase Monitor. Aunque eficaz, la clase Monitor es propensa a que se produzcan bloqueos huérfanos e interbloqueos.

Clase Monitor

La clase Monitor proporciona una funcionalidad adicional, que se puede utilizar junto con la instrucción lock:

  • El método TryEnter permite un subproceso que está bloqueado en espera de que el recurso se interrumpa una vez transcurrido un intervalo de tiempo especificado. Devuelve un valor booleano que indica la finalización correcta o incorrecta, que se puede utilizar para detectar y evitar posibles interbloqueos.

  • Un subproceso de una sección crítica llama al método Wait. Deja el control del recurso y se bloquea hasta que el recurso vuelve a estar disponible.

  • Los métodos Pulse y PulseAll permiten que un subproceso que esté a punto de liberar el bloqueo o de llamar a Wait sitúe uno o más subprocesos en la cola de subprocesos listos, de manera que puedan adquirir el bloqueo.

Los tiempos de espera en sobrecargas de método Wait permiten a los subprocesos en espera situarse en la cola de subprocesos listos.

La clase Monitor puede proporcionar el bloqueo en varios dominios de aplicación si el objeto utilizado para el bloqueo deriva de MarshalByRefObject.

Monitor tiene afinidad de subprocesos. Es decir, un subproceso en el que entró el monitor debe salir llamando a Exit o Wait.

No se pueden crear instancias de la clase Monitor. Sus métodos son estáticos (Shared en Visual Basic) y actúan en un objeto de bloqueo instanciable.

Para obtener información general sobre los conceptos, vea Monitores.

Clase Mutex

Los subprocesos solicitan una Mutex llamando a una sobrecarga de su método WaitOne. Se proporcionan sobrecargas con tiempos de espera para permitir a los subprocesos abandonar la espera. A diferencia de la clase Monitor, una exclusión mutua (mutex) puede ser local o global. Las exclusiones mutuas globales, también denominadas mutex con nombre, son visibles en todo el sistema operativo y se pueden utilizar para sincronizar subprocesos en varios procesos o dominios de aplicación. Las exclusiones mutuas locales derivan de MarshalByRefObject y se pueden utilizar superando los límites del dominio de aplicación.

Además, Mutex deriva de WaitHandle, lo que significa que se puede utilizar con los mecanismos de señalización proporcionados por WaitHandle, como por ejemplo los métodos WaitAll, WaitAny y SignalAndWait.

Como Monitor, Mutex tiene afinidad de subprocesos. A diferencia de Monitor, un Mutex es un objeto instanciable.

Para obtener información general sobre los conceptos, vea Exclusiones mutuas (mutex).

Clase SpinLock

A partir de .NET Framework versión 4, puede utilizar la clase SpinLock cuando la sobrecarga requerida por Monitor disminuya el rendimiento. Si SpinLock encuentra una sección crítica bloqueada, simplemente gira en un bucle hasta que el bloqueo esté de nuevo disponible. Si el bloqueo se mantiene durante un tiempo muy corto, el giro puede proporcionar un mejor rendimiento que el bloqueo. Sin embargo, si el bloqueo se mantiene durante más de unas decenas de ciclos, SpinLock se ejecuta igual de bien que Monitor, pero utilizará más ciclos de CPU y puede disminuir por lo tanto el rendimiento de otros subprocesos o procesos.

Otros bloqueos

No es necesario que los bloqueos no sean exclusivos. Con frecuencia resulta útil permitir el acceso simultáneo a un recurso a un número limitado de subprocesos. Los semáforos y bloqueos de lector-escritor están diseñados para controlar este tipo de acceso a recursos agrupados.

Clase ReaderWriterLock

La clase ReaderWriterLockSlim está dirigida al caso en el que un subproceso que cambia los datos, el sistema de escritura, debe tener acceso exclusivo a un recurso. Cuando el sistema de escritura no está activo, cualquier número de lectores puede obtener acceso al recurso (llamando, por ejemplo, al método EnterReadLock). Cuando un subproceso solicita el acceso exclusivo (llamando, por ejemplo, al método EnterWriteLock), las solicitudes posteriores del lector se bloquean hasta que todos los lectores existentes salen del bloqueo y el sistema de escritura entra y sale del bloqueo.

ReaderWriterLockSlim tiene afinidad de subprocesos.

Para obtener información general sobre los conceptos, vea Bloqueos de lector y escritor.

Clase Semaphore

La clase Semaphore permite a un número especificado de subprocesos tener acceso a un recurso. Los subprocesos adicionales que solicitan el recurso se bloquean hasta que un subproceso libera el semáforo.

Como la clase Mutex, Semaphore deriva de WaitHandle. También, al igual que Mutex, un Semaphore puede ser local o global. Se puede utilizar más allá de los límites del dominio de aplicación.

A diferencia de Monitor, Mutex y ReaderWriterLock, Semaphore no tienen afinidad de subprocesos. Esto significa se puede utilizar en escenarios en los que un subproceso adquiere el semáforo y otro lo libera.

Para obtener información general sobre los conceptos, vea Semaphore y SemaphoreSlim.

System.Threading.SemaphoreSlim es un semáforo ligero para sincronización dentro del límite de un único proceso.

Volver al principio

Señalización

La manera más sencilla de esperar una señal de otro subproceso es llamar al método Join, que se bloquea hasta que se complete el otro subproceso. Join tiene dos sobrecargas que permiten al subproceso bloqueado interrumpir la espera una vez transcurrido un intervalo especificado.

Los identificadores de espera proporcionan un conjunto mucho más rico de capacidades de espera y señalización.

Controladores de espera

Los identificadores de espera derivan de la clase WaitHandle, que a su vez deriva de MarshalByRefObject. Así, los identificadores de espera se pueden utilizar para sincronizar las actividades de los subprocesos fuera de los límites del dominio de aplicación.

Los subprocesos se bloquean en los identificadores de espera llamando al método de instancia WaitOne o uno de los métodos estáticos WaitAll, WaitAny o SignalAndWait. La forma de liberación depende de qué método se llamó y del tipo de identificadores de espera.

Para obtener información general sobre los conceptos, vea Controladores de espera.

Identificadores de espera de evento

Los identificadores de espera de evento incluyen la clase EventWaitHandle y sus clases derivadas, AutoResetEvent y ManualResetEvent. Los subprocesos se liberan desde un identificador de espera de eventos cuando el identificador de espera de eventos está señalado llamando a su método Set o utilizando el método SignalAndWait.

Los identificadores de espera de eventos se pueden restablecer automáticamente, como un torniquete que sólo permite una lectura cada vez que se señala, o se debe restablecer manualmente, como una puerta que está cerrada hasta que se señala y luego queda abierta hasta que alguien la cierra. Cuando sus nombres implican, AutoResetEvent y ManualResetEvent representan el primer caso y el último, respectivamente. System.Threading.ManualResetEventSlim es un evento ligero para sincronización dentro del límite de un único proceso.

EventWaitHandle puede representar cualquier tipo de evento y puede ser local o global. Las clases derivadas AutoResetEvent y ManualResetEvent siempre son locales.

Los identificadores de espera de evento no tienen afinidad de subprocesos. Cualquier subproceso puede señalar un identificador de espera de evento.

Para obtener información general sobre los conceptos, vea EventWaitHandle, AutoResetEvent, CountdownEvent y ManualResetEvent.

Clases Mutex y Semaphore

Dado que las clases Mutex y Semaphore derivan de WaitHandle, se pueden utilizar con los métodos estáticos de WaitHandle. Por ejemplo, un subproceso puede utilizar el método WaitAll para esperar hasta que se cumplen las tres condiciones siguientes: se señala un EventWaitHandle, se libera una Mutex y se libera un Semaphore. De forma parecida, un subproceso puede utilizar el método WaitAny para esperar hasta que se cumple cualquiera de esas condiciones.

En el caso de una Mutex o un Semaphore, ser señalizados significa ser liberados. Si cualquiera de los tipos se utiliza como el primer argumento del método SignalAndWait, se libera. En el caso de Mutex, que tiene afinidad de subprocesos, se inicia una excepción si el subproceso que llama no es el propietario de la exclusión mutua. Como se ha indicado antes, los semáforos no tienen afinidad de subprocesos.

Barrera

La clase Barrier proporciona una manera de sincronizar varios subprocesos de forma cíclica para que todos se bloqueen en el mismo punto y esperen a que se completen los demás subprocesos. Una barrera es útil cuando uno o más subprocesos requieren los resultados de otro subproceso antes de proseguir con la siguiente fase de un algoritmo. Para obtener más información, vea Barrier (.NET Framework).

Volver al principio

Tipos de sincronización ligeros

A partir de .NET Framework 4, puede utilizar primitivas de sincronización que proporcionan un rendimiento rápido al evitar, dentro de lo posible, una costosa dependencia de los objetos de kernel de Win32 como, por ejemplo, los identificadores de espera. En general, estos tipos se deben utilizar cuando los tiempos de espera son cortos y solo si, una vez probados los tipos de sincronización originales, se consideren poco satisfactorios. Los tipos ligeros no se pueden utilizar en escenarios que requieran una comunicación entre procesos.

Volver al principio

SpinWait

A partir de .NET Framework 4, puede utilizar la estructura System.Threading.SpinWait cuando un subproceso tenga que esperar a que se señale un evento o se cumpla una condición, pero siempre que se prevea que el tiempo de espera real sea menor que el tiempo de espera requerido utilizando un identificador de espera o bloqueando el subproceso actual. Al utilizar SpinWait, puede especificar un período de tiempo corto para girar durante la espera y generar luego (por ejemplo, al esperar o estar en suspensión) solo si la condición no se cumplió en el tiempo especificado.

Volver al principio

Operaciones de interbloqueo

Las operaciones interbloqueadas son operaciones atómicas simples realizadas en una ubicación de memoria por métodos estáticos de la clase Interlocked. Esas operaciones atómicas incluyen suma, incremento y decremento, intercambio, intercambio condicional que depende de una comparación, y operaciones de lectura para los valores de 64 bits en plataformas de 32 bits.

NotaNota

La garantía de atomicidad está limitada a operaciones individuales; cuando varias operaciones se deben realizar como una unidad, se debe utilizar un mecanismo de sincronización más amplio.

Aunque ninguna de estas operaciones son bloqueos o señales, se pueden utilizar para construir bloqueos y señales. Dado que son nativas del sistema operativo Windows, las operaciones interbloqueadas son sumamente rápidas.

Las operaciones interbloqueadas se pueden utilizar con garantías de memoria volátil para escribir aplicaciones que poseen una simultaneidad sin bloqueos eficaz. Sin embargo, requieren una sofisticada programación a bajo nivel por lo que, para la mayoría de los usos, los bloqueos simples son una mejor opción.

Para obtener información general sobre los conceptos, vea Operaciones de bloqueo.

Volver al principio

Vea también

Conceptos

Sincronizar datos para subprocesamiento múltiple

Monitores

Exclusiones mutuas (mutex)

Semaphore y SemaphoreSlim

Controladores de espera

Operaciones de bloqueo

Bloqueos de lector y escritor

Barrier (.NET Framework)

SpinLock

Otros recursos

EventWaitHandle, AutoResetEvent, CountdownEvent y ManualResetEvent

SpinWait