C++ de los fundamentos al dominio — Lección 20
Gestionar concurrencia con hilos, atomics y el modelo de memoria
Los artículos de este blog se han escrito con fines didácticos. Pueden contener errores; contrasta la información con el compilador y la biblioteca estándar de tu plataforma.
Cuando varios hilos acceden a la misma memoria, el problema supera un orden de ejecución impredecible. En el C++ memory model, una data race sin sincronización es undefined behavior.
Proteger invariantes compartidas
class Inventory
{
public:
void Add(Item item)
{
std::lock_guard lock(Mutex);
Items.push_back(std::move(item));
}
private:
std::mutex Mutex;
std::vector<Item> Items;
};
std::lock_guard libera el mutex al salir del scope, también con excepciones. El lock debe proteger la invariante completa, no una sola llamada al vector.
Adquirir varios mutex en órdenes diferentes puede causar deadlock. Bloquéalos juntos con std::scoped_lock o establece un orden global.
Atomics para valores pequeños e independientes
std::atomic<unsigned int> completedJobs{0};
void MarkCompleted()
{
completedJobs.fetch_add(1, std::memory_order_relaxed);
}
Un contador estadístico que no publica otros datos puede usar relaxed ordering. Un atomic no vuelve coherente automáticamente un objeto con varios campos.
La sequential consistency predeterminada es más fácil de razonar. Acquire, release y relaxed deben partir de una relación happens-before demostrada, no de una suposición de rendimiento.
Vincular el cierre a la vida con jthread
void Worker(std::stop_token stop)
{
while (!stop.stop_requested())
ProcessOneJob();
}
void Run()
{
std::jthread worker(Worker);
// El destructor solicita parada y hace join.
}
std::jthread de C++20 solicita la parada y espera durante la destrucción. stop_token es cooperativo; un I/O bloqueante aún necesita timeout o un mecanismo para despertarlo.
El primer paso es reducir estado mutable compartido. Da entradas independientes a los workers y reúne resultados mediante mensajes o una queue. Incluso atomics separados pueden sufrir false sharing en la misma cache line.
Una ejecución correcta no demuestra seguridad. Documenta propiedad, cierre, orden de locks y publicación, y verifícalos con ThreadSanitizer y pruebas de estrés reproducibles.