¿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!
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ónFun(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 localmyID
. La función luego imprime el valor demyID
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 establecei
como argumento para la funciónFun
, 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 parai
. No se garantiza el orden en el que se ejecutan los hilos, por lo que el valor almacenado eni
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, yi
se ha incrementado a 3. Los tres hilos hacen referencia a la misma ubicación de memoria parai
, 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 pasari
al constructor destd::thread
. Esto crea un envoltorio de referencia que el constructor destd::thread
puede desempaquetar al crear el hilo.Aquí hay una versión actualizada del bucle que crea hilos utilizando
std::ref
:Con este cambio, la salida sería la esperada:
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 variablei
. Esto garantiza que no haya una condición de carrera para la variable, ya que se actualiza atómicamente.Con este cambio y actualizando la función
Fun
para que tome unstd::atomic<int>*
en lugar de unint*
, la salida también sería la esperada: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.