Semaphore y SemaphoreSlim

La clase System.Threading.Semaphore representa un semáforo local o con nombre (para todo el sistema). Se trata de un contenedor fino alrededor del objeto semáforo de Win32. Los semáforos de Win32 son semáforos de recuento, que se pueden utilizar para controlar el acceso a un grupo de recursos.

La clase SemaphoreSlim representa un semáforo ligero y rápido que se puede usar para esperar en un único proceso cuando se supone que los tiempos de espera serán muy cortos. SemaphoreSlim se basa en la medida de lo posible en las primitivas de sincronización proporcionadas por Common Language Runtime (CLR). Sin embargo, también proporciona identificadores de espera basados en kernel e inicializados de forma diferida a fin permitir la espera en varios semáforos. SemaphoreSlim también admite el uso de tokens de cancelación, pero no los semáforos con nombre ni el uso de un identificador de espera para sincronización.

Administrar un recurso limitado

Los subprocesos entran en el semáforo mediante la llamada al método WaitOne, heredado de la clase WaitHandle, si se trata de un objeto System.Threading.Semaphore, o al método SemaphoreSlim.Wait o SemaphoreSlim.WaitAsync, si se trata de un objeto SemaphoreSlim. Cuando se devuelve la llamada, disminuye el recuento en el semáforo. Cuando un subproceso solicita la entrada y el recuento es cero, el subproceso se bloquea. Cuando los subprocesos liberan el semáforo mediante la llamada al método Semaphore.Release o SemaphoreSlim.Release, los subprocesos bloqueados pueden entrar. No hay ningún orden garantizado, como “primero en entrar, primero en salir” (FIFO) o “último en entrar, primero en salir” (LIFO), para que los subprocesos bloqueados entren en el semáforo.

Un subproceso puede entrar varias veces en el semáforo mediante la llamada al método System.Threading.Semaphore del objeto WaitOne o al método SemaphoreSlim del objeto Wait. Para liberar el semáforo, el subproceso puede llamar el mismo número de veces a la sobrecarga de un método Semaphore.Release() o SemaphoreSlim.Release(), o llamar a la sobrecarga del método Semaphore.Release(Int32) o SemaphoreSlim.Release(Int32) y especificar el número de entradas que se liberan.

Los semáforos y la identidad del subproceso

Los dos tipos de semáforo no exigen la identidad del subproceso en las llamadas a los métodos WaitOne, Wait, Release y SemaphoreSlim.Release. Por ejemplo, un escenario de uso común para semáforos implica un subproceso productor y un subproceso consumidor; uno de esos subprocesos siempre incrementa el recuento del semáforo y el otro siempre lo reduce.

Es responsabilidad del programador garantizar que un subproceso no libere el semáforo demasiadas veces. Por ejemplo, supongamos que un semáforo tiene un recuento máximo de dos y que los subprocesos A y B entran el semáforo. Si un error de programación en el subproceso B hace que llame a Release dos veces, ambas llamadas tendrán éxito. El recuento del semáforo está lleno y cuando el subproceso A finalmente llama a Release, se genera SemaphoreFullException.

Semáforos con nombre

El sistema operativo Windows permite que los semáforos tengan nombres. Un semáforo con nombre abarca todo el sistema. Es decir, una vez creado el semáforo con nombre, es visible para todos los subprocesos de todos los procesos. Por lo tanto, el semáforo con nombre puede utilizarse para sincronizar tanto las actividades de procesos como las de subprocesos.

Puede crear un objeto Semaphore que represente un semáforo de sistema con nombre mediante el uso de uno de los constructores que especifica un nombre.

Nota

Dado que los semáforos con nombre abarcan todo el sistema, es posible tener varios objetos Semaphore que representan el mismo semáforo con nombre. Cada vez que llama a un constructor o al método Semaphore.OpenExisting, se crea un nuevo objeto Semaphore. Si se especifica el mismo nombre repetidamente, se crean varios objetos que representan el mismo semáforo con nombre.

Tenga cuidado al utilizar los semáforos con nombre. Dado que abarcan todo el sistema, otro proceso que utilice el mismo nombre puede entrar en el semáforo inesperadamente. Si hubiera código malintencionado ejecutándose en el mismo equipo, dicho código podría utilizar esto como base de un ataque de denegación de servicio.

Utilice la seguridad de control de acceso para proteger un objeto Semaphore que representa un semáforo con nombre, a poder ser mediante un constructor que especifique un objeto System.Security.AccessControl.SemaphoreSecurity. También puede aplicar la seguridad de control de acceso mediante el método Semaphore.SetAccessControl, pero esto deja una ventana de vulnerabilidad entre el momento en que se crea el semáforo y el momento en que se protege. Proteger los semáforos con seguridad de control de acceso ayuda a evitar ataques malintencionados, pero no soluciona el problema de conflictos involuntarios de nombres.

Vea también