Semaphore: qué es, cómo funciona y su uso en la sincronización de hilos

Autor: IT Sectr Publicado: 2026-03-19 Tiempo de lectura: 8 min

Semaphore es un primitivo de sincronización que controla el acceso a un recurso compartido mediante un contador y una cola de hilos en espera. Según Wikipedia, 2024, el semáforo fue propuesto por Edsger Dijkstra en 1965 para resolver problemas de interacción multihilo. La herramienta permite limitar el número de hilos que trabajan simultáneamente con una sección crítica.

Puntos clave

  • Semaphore es un primitivo de sincronización que controla el acceso mediante un contador de permisos.
  • El semáforo binario toma valores 0 y 1, funcionando como una bandera de bloqueo.
  • El semáforo contador permite el acceso simultáneo de un número determinado de hilos.
  • A diferencia de un mutex, un semáforo no está vinculado a un hilo propietario.
  • Deadlock es uno de los principales peligros al usar semáforos incorrectamente.

¿Qué es un Semaphore?

Semaphore es un primitivo de sincronización que utiliza un contador para controlar el acceso a un recurso compartido. El concepto fue propuesto por Edsger Dijkstra en 1965 y se convirtió en la base de todos los mecanismos modernos de sincronización en sistemas operativos.

Definición y propósito

Un semáforo es una variable entera con dos operaciones atómicas: wait (acquire) y signal (release). La operación wait disminuye el contador, mientras que signal lo aumenta. Cuando el contador llega a cero, el hilo que llama a wait se bloquea hasta que otro hilo ejecuta signal.

El propósito principal de un semáforo es proteger las secciones críticas del acceso simultáneo de múltiples hilos. A diferencia de un mutex, un semáforo no requiere vinculación a un hilo propietario, lo que lo hace adecuado para una gama más amplia de tareas de coordinación.

Historia y fundamento teórico

El concepto del semáforo surgió en el contexto del sistema operativo THE, desarrollado en la Technische Hogeschool Eindhoven. Dijkstra formalizó el semáforo como una abstracción matemática, demostrando su suficiencia para implementar cualquier primitivo de sincronización.

¿Cómo funciona un semáforo?

El mecanismo del semáforo se basa en dos operaciones atómicas y una cola de espera interna. Cuando se llama a acquire, el hilo verifica el valor del contador y, o continúa la ejecución, o se bloquea hasta que se libera el recurso.

Contador y operaciones atómicas

Al crear un semáforo, se establece un valor inicial del contador de permisos. Cada llamada a acquire disminuye el contador en 1. Si después de esto el contador se vuelve negativo, el hilo se bloquea. La operación release aumenta el contador y despierta a uno de los hilos en espera.

kotlin
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)

fun accessResource() {
    semaphore.acquire()
    try {
        println("${Thread.currentThread().name} está trabajando")
    } finally {
        semaphore.release()
    }
}

Cola de espera y planificación

Cuando un hilo llama a acquire con un contador en cero, el SO lo coloca en la cola FIFO del semáforo. El hilo pasa al estado BLOCKED, sin consumir tiempo de CPU. Después de una llamada a release, el primer hilo en la cola pasa al estado RUNNABLE y obtiene acceso al recurso.

Tipos de semáforos

En la teoría de sincronización se distinguen dos tipos principales de semáforos: binario y contador. La elección del tipo depende de la tarea específica de gestión de acceso a recursos.

Semáforo binario (Binary Semaphore)

Un semáforo binario solo toma valores 0 y 1. En su comportamiento se asemeja a un mutex, pero sin el requisito de propiedad: cualquier hilo puede ejecutar release. Estos semáforos son convenientes para implementar banderas de disponibilidad y eventos entre hilos.

kotlin
val ready = Semaphore(0)

fun producer() {
    Thread.sleep(1000)
    ready.release()
}

fun consumer() {
    ready.acquire()
    println("Datos listos")
}

Semáforo contador (Counting Semaphore)

Un semáforo contador puede tomar cualquier valor no negativo. Se utiliza para gestionar un conjunto de recursos similares donde hay varias instancias disponibles. Por ejemplo, un grupo de 5 conexiones de red: cada acquire toma una conexión, release la devuelve al grupo.

Los semáforos contadores son indispensables para limitar la velocidad de acceso a servicios externos e implementar grupos de hilos. Permiten controlar con precisión el grado de paralelismo sin gestión manual de hilos.

ParámetroSemáforo binarioSemáforo contador
Rango0 o 1de 0 a N
Hilos simultáneos1hasta N
Aplicaciónseñalización, banderasgrupos de recursos, limitación de velocidad

Semaphore vs Mutex

Los desarrolladores a menudo confunden semáforo y mutex, aunque existen diferencias fundamentales entre ellos. Comprender estas diferencias es críticamente importante para elegir el mecanismo de sincronización correcto en un proyecto.

Principio de propiedad

La diferencia clave es el concepto de propiedad. Un mutex siempre sabe qué hilo lo ha adquirido, y solo ese hilo puede liberarlo. Un semáforo no tiene propietario: cualquier hilo puede llamar a release sin siquiera haber llamado a acquire. Esto hace que el mutex sea más seguro para la protección de datos y el semáforo más flexible para la coordinación.

Rendimiento y casos de uso

En la práctica, un mutex es más rápido para la exclusión mutua simple gracias a las optimizaciones para escenarios típicos. Un semáforo requiere una sobrecarga adicional para mantener el contador. Sin embargo, para limitar el paralelismo o implementar el patrón productor-consumidor, un semáforo es indispensable.

CaracterísticaSemaphoreMutex
Propiedadsin propietariotiene propietario
Liberacióncualquier hilosolo el hilo propietario
Contadorde 0 a Nbinario
Caso de usolimitación de paralelismo y señalizaciónprotección de sección crítica
Recursiónnosí (reentrante)

Semaphore en el desarrollo móvil

En el desarrollo de aplicaciones móviles, Semaphore se utiliza para gestionar el acceso a recursos limitados: conexiones de red, archivos, bases de datos y componentes de hardware. Las plataformas modernas proporcionan implementaciones integradas convenientes.

Grupo de conexiones al servidor

Un caso de uso típico es un grupo de conexiones HTTP. Una aplicación puede enviar no más de 4 solicitudes simultáneas a un servidor porque la API del proveedor limita el paralelismo. Un semáforo con un valor inicial de 4 garantiza que bajo cualquier carga, el número de solicitudes concurrentes no exceda el límite, mientras que otros hilos esperan en la cola.

Sin un semáforo, un aumento brusco de la actividad del usuario podría causar una sobrecarga repentina en la infraestructura del servidor, lo que lleva a tiempos de espera y errores 429 Too Many Requests. Semaphore actúa como un fusible, permitiendo estrictamente un número especificado de llamadas concurrentes independientemente del número de hilos activos.

Semaphore en Kotlin para Android

Android proporciona la clase Semaphore del paquete java.util.concurrent. Veamos un ejemplo de limitación de solicitudes de red concurrentes a dos hilos para evitar la sobrecarga del servidor.

kotlin
class ApiClient {
    private val throttle = Semaphore(2)

    suspend fun fetch(url: String): Result {
        throttle.acquire()
        return try {
            httpGet(url)
        } finally {
            throttle.release()
        }
    }
}

DispatchSemaphore en Swift para iOS

En iOS, DispatchSemaphore de GCD resuelve la misma tarea. Los desarrolladores lo utilizan para sincronizar el acceso a recursos en código asíncrono sin bloquear el hilo principal.

swift
let semaphore = DispatchSemaphore(value: 3)

func processBatch(_ items: [UIImage]) {
    for img in items {
        semaphore.wait()
        DispatchQueue.global().async {
            applyFilter(to: img)
            semaphore.signal()
        }
    }
}

Errores típicos al trabajar con semáforos

El error más común es un release olvidado cuando ocurre una excepción. Si un hilo termina con un error antes de llamar a release, el semáforo permanece bloqueado permanentemente para otros hilos. Use try/finally o defer para garantizar la liberación. El segundo problema es el deadlock al adquirir múltiples semáforos en diferente orden por diferentes hilos.

Patrones de uso de semáforos

Los semáforos no solo se utilizan para la protección de datos, sino también para la coordinación de hilos en escenarios multihilo complejos. Conocer los patrones comunes acelera el desarrollo y reduce la probabilidad de errores de sincronización.

Existen varios patrones probados de uso de semáforos en proyectos reales. Conocerlos ayuda a evitar errores típicos y construir sistemas multihilo fiables.

Limitador de velocidad (Rate Limiter)

Un semáforo con un valor inicial de N y liberación periódica mediante un temporizador implementa la limitación de velocidad de solicitudes a una API. Por ejemplo, un servicio permite 10 solicitudes por segundo: el semáforo comienza en 10, cada solicitud disminuye el contador, y un TimerTask separado devuelve el contador a su valor inicial cada segundo. Esto protege tanto a la aplicación como al servidor de la sobrecarga.

Productor-Consumidor con semáforos

En el problema clásico del productor-consumidor, dos semáforos gestionan un búfer: empty (permisos de escritura) y full (permisos de lectura). El productor llama a acquire en empty y release en full, mientras que el consumidor hace lo contrario. Este esquema garantiza que el consumidor nunca lea un búfer vacío y el productor nunca lo desborde.

Este mismo esquema subyace al búfer acotado en los sistemas operativos — un búfer circular de tamaño fijo. En aplicaciones móviles, el patrón se utiliza para procesar colas de imágenes, archivos de video y eventos analíticos.

Limitación de solicitudes de red

Los semáforos se utilizan con éxito para la limitación de llamadas de red en servicios en segundo plano. Por ejemplo, una aplicación de análisis envía paquetes de eventos al servidor. Sin limitar los hilos concurrentes durante picos de carga (inicio de la aplicación, sincronización después de estar desconectado), el número de solicitudes simultáneas puede exceder los límites del servidor. Un semáforo con un valor inicial de 3 garantiza un envío fluido y previene el bloqueo del lado del servidor.

Preguntas frecuentes

¿Cuál es la diferencia entre un Semaphore y un contador normal?

Semaphore no es solo un contador, sino un primitivo de sincronización con operaciones atómicas y una cola de espera. Un contador normal no bloquea un hilo ni garantiza la atomicidad del incremento cuando varios hilos acceden concurrentemente.

¿Puede un semáforo causar un deadlock?

Sí, el deadlock es posible al adquirir múltiples semáforos en diferente orden por diferentes hilos. Por ejemplo, el hilo A adquiere S1, luego S2, mientras que el hilo B adquiere S2, luego S1. Fije un orden único de adquisición para todos los semáforos en el proyecto.

¿Qué sucede cuando se llama a acquire con un contador en cero?

El hilo se bloquea y entra en estado de espera. No consume tiempo de CPU hasta que otro hilo llama a release. En Java, este es el estado BLOCKED; en Swift, el hilo es suspendido por GCD.

¿En qué se diferencia fundamentalmente un Binary Semaphore de un Mutex?

La principal diferencia es la propiedad. Un Mutex solo puede ser liberado por el hilo propietario. Un Binary Semaphore puede ser liberado por cualquier hilo, lo que es conveniente para la señalización entre hilos pero menos seguro para proteger la integridad de los datos.

¿Qué valor inicial debo elegir para el contador?

El valor inicial depende del escenario. Para proteger un solo recurso — 1. Para un grupo de N conexiones — N. Para señalización entre hilos, use 0 para que el hilo consumidor espere una señal del productor.

Resumen

  • Semaphore es un primitivo de sincronización basado en contador propuesto por Dijkstra en 1965.
  • El semáforo binario toma valores 0 y 1; el semáforo contador toma cualquier valor no negativo.
  • Las operaciones acquire y release son atómicas y seguras para hilos.
  • A diferencia de un mutex, un semáforo no está vinculado a un hilo propietario.
  • Los semáforos contadores se utilizan para gestionar grupos de recursos y limitar el paralelismo.
  • El release olvidado es el error más común que lleva a la congelación de hilos.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también