Semáforos en C++20: control de concurrencia y señalización entre hilos

Fuentes: Understanding std::counting_semaphore and std::binary_semaphore from C++20

El estándar C++20 incorporó al lenguaje dos tipos de semáforos pensados para resolver problemas clásicos de concurrencia que un mutex no cubre con soltura: std::counting_semaphore y std::binary_semaphore. Este artículo explica, con ejemplos prácticos en C++23, cómo utilizarlos para limitar el número de hilos que ejecutan una operación costosa al mismo tiempo y cómo enviar señales entre hilos.

std::counting_semaphore dispone de un contador interno y de operaciones como acquire(), release(), try_acquire(), try_acquire_for() y try_acquire_until(). El primer caso de uso presentado limita a tres los accesos simultáneos a una sección que representa, por ejemplo, conexiones a una base de datos: ocho hilos trabajadores llaman a acquire(), pero solo tres progresan mientras el resto espera; cada release() libera un hueco. El semáforo no impone exclusión mutua total, sino un límite configurable de concurrencia. Para evitar fugas de slots por retornos anticipados o excepciones, se muestra un pequeño RAII guard (SemaphoreGuard) cuyo destructor llama a release().

std::binary_semaphore es un alias de counting_semaphore<1> y, al no exigir que el hilo que llama a release() sea el mismo que llamó a acquire(), resulta adecuado para señalización unidireccional. Un ejemplo muestra un hilo trabajador que espera una señal de arranque desde main, calcula un resultado y emite una señal de finalización que main espera antes de leer el dato; el semáforo actúa además como barrera de memoria, garantizando visibilidad de las escrituras del trabajador.

Finalmente se introducen las esperas con tiempo máximo (try_acquire_for y try_acquire_until), útiles cuando un hilo no puede bloquearse indefinidamente y debe poder activar rutas de fallback o reintentos.