Programación Multihilo en C++: std::thread, std::shared_mutex y std::unique_lock

Desde C++11, el manejo de hilos se simplifica con la biblioteca <thread>. A continuación se muestra un ejemplo básico:

std::thread(MiFuncion);          // Lanza un hilo directamente
std::thread hilo(MiFuncion);     // Declara un objeto hilo
hilo.detach();                   // El hilo se ejecuta en segundo plano

Existen dos formas de esperar la finalización de un hilo:

  • detach: el hilo se ejecuta de forma independiente, el programa continúa sin esperar. El objeto std::thread se separa del hilo subyacente; el hilo se comporta como un demonio (daemon). Después de detach, joinable() devuelve false.
  • join: el programa espera a que el hilo termine antes de continuar. Solo se puede llamar en un hilo activo (joinable() == true). Cada join solo puede ejecutarse una vez; posteriormente joinable() se vuelve false.

Cada objeto std::thread válido corresponde a un hilo del sistema. Un hilo puede estar en estado unible (joinable) o no unible (non‑joinable). Un hilo unible debe ser esperado mediante join() o separado con detach() antes de que el objeto se destruya; de lo contrario se lanza una excepción (std::terminate).

Por ejemplo, en la siguiente función:

constexpr auto LIMITE = 10000000;

bool ProcesarDatos(std::function<bool(int)> filtro,
                   int maximo = LIMITE)
{
    std::vector<int> valoresValidos;

    std::thread hilo([&filtro, maximo, &valoresValidos]
                     {
                         for (int i = 0; i <= maximo; ++i)
                             if (filtro(i))
                                 valoresValidos.push_back(i);
                     });

    auto manejadorNat = hilo.native_handle();  // Para configurar prioridad, etc.
    // ...

    if (condicionesCumplidas()) {
        hilo.join();
        RealizarCalculo(valoresValidos);
        return true;
    }
    // Si no se cumplen condiciones, el hilo no se une ni se separa → error
    return false;
}

Si condicionesCumplidas() es falsa, el hilo nunca se une ni se separa, lo que causa una terminación anormal. Esto demuestra que std::thread exige decidir explícitamente entre join o detach.

Un hilo pasa a ser no unible en los siguientes casos:

  • Hilos construidos por defecto (sin función asociada)
  • Hilos movidos a otro objeto (std::move)
  • Hilos sobre los que se ha invocado join() o detach()

Paso de parámetros a hilos

Los argumentos se pasan directamente al constructor de std::thread:

void Operacion(int a, int b);
std::thread hilo(Operacion, 10, 20);

Por defecto, los argumentos se copian en el espacio del hilo. Si se desea pasar una referencia, se debe usar std::ref:

void Incrementar(int& valor);
int dato = 5;
std::thread hilo(Incrementar, std::ref(dato));
hilo.join();

std::thread es movible pero no copiable. Se puede transferir la propiedad con std::move.

Protección de datos compartidos: mutex y locks

Para evitar condiciones de carrera, C++ ofrece std::mutex (C++11) y std::shared_mutex (C++17). std::mutex no es copiable ni movible.

std::shared_mutex permite que varios hilos lean simultáneamente (bloqueo compartido), mientras que la escritura requiere un bloqueo exclusivo. Para lectura se usa std::shared_lock (C++14) y para escritura std::unique_lock (C++11) o std::lock_guard (C++11).

std::unique_lock es más flexible que std::lock_guard; permite bloquear y desbloquear manualmente. std::scoped_lock (C++17) maneja varios mutex a la vez, evitando interbloqueos mediante un algoritmo de reintento: si no puede adquirir todos, libera los ya obtenidos y vuelve a intentar.

std::shared_mutex mutexCompartido;

void LeerDatos() {
    std::shared_lock<std::shared_mutex> lock(mutexCompartido);
    // Lectura segura
}

void EscribirDatos() {
    std::unique_lock<std::shared_mutex> lock(mutexCompartido);
    // Escritura exclusiva
}

Estos mecanismos siguen el patrón RAII: el bloqueo se adquiere en la construcción y se libera en la destrucción, garantizando una gestión correcta incluso ante excepciones.

Etiquetas: std::thread std::shared_mutex std::unique_lock std::lock_guard C++11

Publicado el 6-10 18:48