Share via


Información general sobre los primitivos de sincronización

Actualización: noviembre 2007

.NET Framework proporciona un intervalo de primitivos de sincronización para controlar las interacciones de subprocesos y evitar las condiciones de anticipación. É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 y 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 bloques. 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.

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).

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 Semáforos.

Señalización

La forma más sencilla de esperar una señal de otro subproceso consiste en llamar al método Join, que se bloquea hasta que finaliza el otro subproceso. Join tiene dos sobrecargas que permiten que el subproceso bloqueado salga del estado de espera una vez transcurrido un intervalo de tiempo 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.

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

Operaciones interbloqueadas

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.

Nota:

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 eficaz concurrencia sin bloqueo; sin embargo, requieren una sofisticada programación a bajo nivel y, 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.

Vea también

Conceptos

Sincronizar datos para subprocesamiento múltiple

Monitores

Exclusiones mutuas (mutex)

Semáforos

Controladores de espera

Operaciones de bloqueo

Bloqueos de lector y escritor

Otros recursos

EventWaitHandle, AutoResetEvent y ManualResetEvent