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::threadse separa del hilo subyacente; el hilo se comporta como un demonio (daemon). Después dedetach,joinable()devuelvefalse. - join: el programa espera a que el hilo termine antes de continuar. Solo se puede llamar en un hilo activo (
joinable() == true). Cadajoinsolo puede ejecutarse una vez; posteriormentejoinable()se vuelvefalse.
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()odetach()
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.