Biblioteca estándar de C++<mutex>

En la programación multihilo, garantizar la consistencia de los datos y la seguridad de los subprocesos es crucial.

La biblioteca estándar de C++ proporciona un rico conjunto de primitivas de sincronización para controlar el acceso a los recursos compartidos.

En la biblioteca estándar de C++,<mutex>El archivo de cabecera proporciona un conjunto de herramientas para implementar la sincronización y exclusión mutua entre subprocesos en programas multihilo.

<mutex>El archivo de cabecera fue introducido en C++11 y contiene clases y funciones para exclusión mutua (mutex). El mutex es un mecanismo de sincronización que se utiliza para evitar que varios subprocesos accedan simultáneamente a recursos compartidos.

El mutex es una primitiva de sincronización utilizada para controlar el acceso a los recursos compartidos. Cuando un subproceso necesita acceder a un recurso compartido, intenta bloquear el mutex. Si el mutex ya está bloqueado por otro subproceso, el subproceso solicitante se bloqueará hasta que se libere el mutex.

Sintaxis básica

En C++,<mutex>El archivo de cabecera proporciona las siguientes clases principales:

  • std::mutex: mutex básico.
  • std::recursive_mutex: mutex recursivo, permite que el mismo subproceso lo bloquee varias veces.
  • std::timed_mutex: mutex con función de tiempo de espera.
  • std::recursive_timed_mutex: mutex recursivo con función de tiempo de espera.

Ejemplo

1. Usostd::mutex

A continuación se muestra un ejemplo sencillo que demuestra cómo usar en C++std::mutexpara sincronizar el acceso a los recursos compartidos.

Ejemplo

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // mutex global
int shared_resource = 0;

void increment() {
    for (int i = 0; i < 10000; ++i) {
        mtx.lock(); // bloquear el mutex
        ++shared_resource;
        mtx.unlock(); // desbloquear el mutex
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Final value of shared_resource: " << shared_resource << std::endl;
    return 0;
}

Salida:

Final value of shared_resource: 20000

2. Usostd::recursive_mutex

El mutex recursivo permite que el mismo subproceso bloquee el mismo mutex varias veces. A continuación se muestra un uso destd::recursive_mutexejemplo.

Ejemplo

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex rmtx; // crear un objeto mutex recursivo
int shared_resource = 0; // recurso compartido

// función recursiva
void recursive_increment(int count) {
    if (count <= 0) return;

    std::lock_guard<std::recursive_mutex> lock(rmtx); // bloquear para garantizar la seguridad del subproceso
    ++shared_resource;
    std::cout << "Incremented shared_resource to " << shared_resource << " (count = " << count << ")" << std::endl;

    // llamada recursiva
    recursive_increment(count - 1);
}

int main() {
    std::thread t1(recursive_increment, 3); // el subproceso t1 ejecuta recursive_increment(3)
    std::thread t2(recursive_increment, 3); // el subproceso t2 ejecuta recursive_increment(3)
   
    t1.join(); // esperar a que el subproceso t1 termine
    t2.join(); // esperar a que el subproceso t2 termine

    std::cout << "Final value of shared_resource: " << shared_resource << std::endl;

    return 0;
}

Salida:

Incremented shared_resource to 1 (count = 3)
Incremented shared_resource to 2 (count = 2)
Incremented shared_resource to 3 (count = 1)
Incremented shared_resource to 4 (count = 0)
Incremented shared_resource to 5 (count = 3)
Incremented shared_resource to 6 (count = 2)
Incremented shared_resource to 7 (count = 1)
Incremented shared_resource to 8 (count = 0)
Final value of shared_resource: 8

Análisis del código:

  1. Crearstd::recursive_mutexobjeto:std::recursive_mutex rmtx;Es un mutex recursivo que permite que el mismo subproceso obtenga el bloqueo varias veces.

  2. Recurso compartido:int shared_resource = 0;Es un recurso al que acceden varios subprocesos.

  3. Función recursivarecursive_increment:

    • usastd::lock_guard<std::recursive_mutex>Sobrermtxpara bloquear. Dado que se usa un mutex recursivo, el mismo subproceso puede obtener el bloqueo varias veces.
    • mediante++shared_resourceSe incrementa el valor del recurso compartido.
    • La función se llama a sí misma recursivamente, lo que demuestra cómo una función recursiva puede trabajar de forma segura con múltiples bloqueos.
  4. Crear y ejecutar subprocesos:

    • std::thread t1(recursive_increment, 3);ystd::thread t2(recursive_increment, 3);Se crean dos subprocesos por separado, ambos ejecutaránrecursive_increment(3)。
    • t1.join()yt2.join()Espera a que ambos subprocesos terminen.

Notas

  • Asegúrese de desbloquear el mutex después de cada bloqueo para evitar interbloqueos.
  • Usarstd::lock_guardostd::unique_lockPuede gestionar automáticamente el bloqueo y desbloqueo del mutex, reduciendo la posibilidad de errores.
  • Evite llamar a funciones que puedan lanzar excepciones mientras se mantiene el mutex, ya que esto puede provocar un interbloqueo.

<mutex>Es un archivo de cabecera muy importante en la biblioteca estándar de C++. Proporciona mecanismos básicos de sincronización para la programación multihilo. Mediante el uso de mutex, podemos garantizar que el acceso a los recursos compartidos sea seguro, evitando así problemas de carrera de datos e inconsistencias.


Más explicaciones

1. std::mutex

Proporciona un mutex básico, garantizando que solo un subproceso pueda acceder a un recurso compartido al mismo tiempo.

Ejemplo

#include <mutex>

std::mutex mtx;

void thread_function() {
    std::lock_guard<std::mutex> lock(mtx);
    // acceder al recurso compartido
}

2. std::recursive_mutex

Permite que el mismo subproceso obtenga el bloqueo varias veces sin causar interbloqueos, lo que es especialmente útil para funciones recursivas.

Ejemplo

#include <mutex>

std::recursive_mutex rmtx;

void recursive_function(int count) {
    if (count <= 0) return;
    std::lock_guard<std::recursive_mutex> lock(rmtx);
    // llamada recursiva
    recursive_function(count - 1);
}

3. std::timed_mutex

Proporciona un mutex con tiempo de espera, que permite establecer un tiempo límite al intentar obtener el bloqueo.

Ejemplo

#include <mutex>
#include <chrono>

std::timed_mutex tm;

void try_lock_for_example() {
    if (tm.try_lock_for(std::chrono::seconds(1))) {
        // bloqueo obtenido con éxito
        tm.unlock();
    } else {
        // fallo al obtener el bloqueo
    }
}

4. std::recursive_timed_mutex

Hereda de std::timed_mutex, permite que el mismo subproceso obtenga el bloqueo varias veces y admite funciones de temporización.

Ejemplo

#include <mutex>
#include <chrono>

std::recursive_timed_mutex rtm;

void recursive_timed_function(int count) {
    if (count <= 0) return;
    if (rtm.try_lock_for(std::chrono::seconds(1))) {
        // bloqueo obtenido con éxito
        recursive_timed_function(count - 1);
        rtm.unlock();
    } else {
        // fallo al obtener el bloqueo
    }
}

5. std::lock_guard

Un envoltorio que gestiona automáticamente el bloqueo de std::mutex, utiliza el estilo RAII y garantiza que el bloqueo se libere automáticamente al final del ámbito.

Ejemplo

#include <mutex>

std::mutex mtx;

void function() {
    std::lock_guard<std::mutex> lock(mtx);
    // acceder al recurso compartido
}

6. std::unique_lock

Proporciona una gestión de bloqueo más flexible que std::lock_guard, permite liberar y volver a adquirir el bloqueo manualmente, y también admite bloqueo con tiempo de espera.

Ejemplo

#include <mutex>
#include <chrono>

std::mutex mtx;

void function() {
    std::unique_lock<std::mutex> lock(mtx);
    // acceder al recurso compartido
   
    // se puede liberar el bloqueo manualmente
    lock.unlock();
   
    // se puede volver a adquirir el bloqueo
    lock.lock();
   
    // se puede realizar un bloqueo con tiempo de espera
    if (lock.try_lock_for(std::chrono::seconds(1))) {
        // bloqueo obtenido con éxito
    }
}

7. std::adopt_lock_t

Tipo de flag, utilizado para especificar que std::unique_lock adopte un bloqueo existente.

Ejemplo

std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);

8. std::defer_lock_t

Función: tipo de flag, utilizado para bloqueo diferido; no bloquea durante la inicialización.

Ejemplo

std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// se puede llamar a lock.lock() en algún momento posterior

9. std::try_to_lock_t

Tipo de flag, utilizado para intentar bloquear sin bloquearse.

Ejemplo

std::mutex mtx1, mtx2;
std::unique_lock<std::mutex> lock1(mtx1, std::try_to_lock);
std::unique_lock<std::mutex> lock2(mtx2, std::try_to_lock);

if (lock1 && lock2) {
    // se obtuvieron dos bloqueos con éxito
}
Otras extensiones