es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿Por qué hay una condición de carrera en este fragmento de código con múltiples hilos?

Tengo este código en C++ que utiliza multithreading pero no estoy seguro de por qué obtengo la salida que estoy obteniendo.

void Fun(int* var) {
    int myID;
    myID = *var;
    std::cout << "ID del Hilo: " << myID << std::endl;

}

int main()
{
    using ThreadVector = std::vector<std::thread>;
    ThreadVector tv;
    std::cout << std::thread::hardware_concurrency() << std::endl;
    for (int i = 0; i < 3 ; ++i)
    {
        auto th = std::thread(&Fun, &i);
        tv.push_back(std::move(th));
    }
    for (auto& elem : tv)
    {
        elem.join();
    }

}

Me pregunto si existe una condición de carrera para la variable i, y si es así, ¿cómo se entrelaza? Intenté compilarlo y constantemente obtuve la impresión del ID del hilo como 3, pero me sorprendió porque pensé que la variable tenía que ser global para ser accedida por los diferentes hilos nuevos.

Esto es lo que pensé que sucedería: se crea el hilo 1, Fun comienza a ejecutarse en el hilo 1 con myID = 0, el hilo principal continúa ejecutándose e incrementa i, se crea el segundo hilo y myID para ese sería myID = 1… y así sucesivamente. Y por lo tanto, la salida sería el myID en incrementos, es decir, 1, 2, 3.

Sé que puedo resolver esto con std::lock_guard, pero solo me pregunto cómo ocurre el entrelazado (CARGA, INCREMENTO, ALMACENAMIENTO) que causa esta condición de carrera para la variable i.

Cualquier ayuda es apreciada, ¡gracias!

Tags:  , ,

Answer

  1. Avatar for davy.ai

    Explicación del código y posible condición de carrera

    El código anterior crea tres hilos utilizando la clase std::thread para ejecutar la función Fun(int*) de forma concurrente. Cada hilo toma como argumento un puntero a un entero y asigna el valor al que apunta el puntero a una variable local myID. La función luego imprime el valor de myID y el ID del hilo.

    Hay un bucle que crea estos hilos y los agrega a una variable ThreadVector. Luego, los hilos se unen en un bucle separado. El bucle que crea los hilos establece i como argumento para la función Fun, que se pasa por referencia al hilo.

    El problema con el código anterior es que hay una condición de carrera para la variable i. El bucle crea tres hilos, pero todos hacen referencia a la misma ubicación de memoria para i. No se garantiza el orden en el que se ejecutan los hilos, por lo que el valor almacenado en i en el momento de la referencia por cada hilo podría ser diferente.

    En este caso, el valor de i siempre será 3 para todos los hilos porque el bucle se completa antes de que los hilos comiencen a ejecutarse, y i se ha incrementado a 3. Los tres hilos hacen referencia a la misma ubicación de memoria para i, que es 3 en el momento de la ejecución.

    Solución a la condición de carrera

    Una solución a esta condición de carrera es utilizar std::ref al pasar i al constructor de std::thread. Esto crea un envoltorio de referencia que el constructor de std::thread puede desempaquetar al crear el hilo.

    Aquí hay una versión actualizada del bucle que crea hilos utilizando std::ref:

    for (int i = 0; i < 3; ++i)
    {
        auto th = std::thread(&Fun, std::ref(i));
        tv.push_back(std::move(th));
    }
    

    Con este cambio, la salida sería la esperada:

    ID del hilo: 0
    ID del hilo: 2
    ID del hilo: 1
    

    Cada hilo ahora hace referencia a una ubicación de memoria separada para i, que tiene un valor que es el valor esperado en el momento de la ejecución.

    Otra solución es utilizar std::atomic<int> para la variable i. Esto garantiza que no haya una condición de carrera para la variable, ya que se actualiza atómicamente.

    std::atomic<int> i{0}; // Inicializar i como un entero atómico
    
    for (int x = 0; x < 3; ++x)
    {
        auto th = std::thread([&i, x]() { Fun(&i); });
        tv.push_back(std::move(th));
        ++i; // Actualizar el entero atómico después de crear el hilo
    }
    

    Con este cambio y actualizando la función Fun para que tome un std::atomic<int>* en lugar de un int*, la salida también sería la esperada:

    ID del hilo: 0
    ID del hilo: 1
    ID del hilo: 2
    

    Conclusión

    Es importante ser consciente de las condiciones de carrera al crear código con múltiples hilos. Las soluciones mencionadas anteriormente proporcionan formas de garantizar la seguridad de los hilos y evitar condiciones de carrera para variables compartidas.

Comments are closed.