ProgramaciónDesarrollador C++ multihilo/servidor

¿Qué matices se deben tener en cuenta al trabajar con la multihilo en C++? Describe la diferencia entre std::mutex, std::lock_guard, std::unique_lock y proporciona un ejemplo de trabajo seguro con hilos.

Supere entrevistas con el asistente de IA Hintsage

Respuesta

En C++ moderno (desde C++11) existe una rica herramienta para trabajar con la multihilo: std::thread, mutexes y diversos objetos envolventes para gestionar el acceso a recursos. La interacción segura entre hilos requiere entender los siguientes componentes:

  • std::mutex — el primitivo de bloqueo principal para sincronizar acceso. Requiere un lock/unlock explícito.
  • std::lock_guard — un envoltorio RAII sobre un mutex, que automáticamente captura y libera el mutex al entrar/salir del ámbito.
  • std::unique_lock — un envoltorio RAII más versátil que permite bloquear/desbloquear manualmente, así como mover la propiedad del mutex entre hilos y usar condition_variable.

Para evitar condiciones de carrera, siempre usa envoltorios RAII donde sea posible. Ten en cuenta que intentar capturar el mismo mutex nuevamente llevará a un deadlock (si no es un mutex recursivo).

Ejemplo de trabajo correcto:

#include <iostream> #include <mutex> #include <thread> std::mutex mtx; void printThreadSafe(int id) { std::lock_guard<std::mutex> lock(mtx); std::cout << "Thread " << id << std::endl; } int main() { std::thread t1(printThreadSafe, 1); std::thread t2(printThreadSafe, 2); t1.join(); t2.join(); }

Pregunta capciosa

¿Se puede utilizar std::lock_guardstd::mutex para implementar sincronización condicional con std::condition_variable?

Respuesta: ¡No! Para trabajar con std::condition_variable es necesario un std::unique_lockstd::mutex, porque lock_guard no proporciona métodos para controlar el bloqueo (por ejemplo, liberar temporalmente — unlock — y luego volver a capturar), que son necesarios al esperar una notificación.

Ejemplo:

std::mutex mtx; std::condition_variable cv; bool ready = false; void waitForEvent() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{return ready;}); // Procesamiento posterior }

Ejemplos de errores reales debido al desconocimiento de los matices del tema


Historia

En el proyecto se utilizaba trabajo manual con mtx.lock()/mtx.unlock(). Al lanzar una excepción entre lock/unlock, el mutex quedaba bloqueado — muchos hilos "se congelaban". Después de cambiar a RAII (std::lock_guard), el problema desapareció.


Historia

Al notificar a través de std::condition_variable, en lugar de std::unique_lock, se utilizaba std::lock_guard. Como resultado, el programa no se compilaba en nuevos compiladores, y en el antiguo, ocurría un runtime assert debido a una API inconsistente.


Historia

En el código se llamaba unlock() a std::lock_guard para "controlar" manualmente el acceso al mutex. Esto llevaba a un comportamiento indefinido en diferentes compiladores, a veces causando la caída de la aplicación. Se entendió que lock_guard no admite liberación manual, era necesaria la migración a unique_lock.