C ++ Los hilos en el interior del bucle imprimen valores incorrectos

19

Estoy tratando de entender Multi-threading en c ++, pero estoy atrapado en este problema: si inicio hilos en un bucle for, imprimen valores incorrectos. Este es el código:

#include <iostream>
#include <list>
#include <thread>

void print_id(int id){
    printf("Hello from thread %d\n", id);
}

int main() {
    int n=5;
    std::list<std::thread> threads={};
    for(int i=0; i<n; i++ ){
        threads.emplace_back(std::thread([&](){ print_id(i); }));
    }
    for(auto& t: threads){
        t.join();
    }
    return 0;
}

Esperaba que me imprimieran los valores 0,1,2,3,4 pero a menudo obtuve el mismo valor dos veces. Esta es la salida:

Hello from thread 2
Hello from thread 3
Hello from thread 3
Hello from thread 4
Hello from thread 5

¿Qué me estoy perdiendo?

Ermando
fuente
77
Pasar ipor el valor de lambda, [i].
rafix07
1
Vale la pena señalar que su uso de emplace_backes extraño: emplace_backtoma una lista de argumentos y la pasa a un constructor para std::thread. Ha pasado una instancia (rvalue) de std::thread, por lo tanto, construirá un hilo y luego lo moverá al vector. Esa operación se expresa mejor por el método más común push_back. Sería más sensato escribir threads.emplace_back([i](){ print_id(i); });(construir en el lugar) o threads.push_back(std::thread([i](){ print_id(i); }));(construir + mover), que son algo más idiomáticos.
Milo Brandt

Respuestas:

17

La [&]sintaxis está haciendo ique se capture por referencia . Por lo tanto, con bastante frecuencia i, se avanzará más cuando se ejecute el hilo de lo que cabría esperar. Más en serio, el comportamiento de su código no está definido si isale del alcance antes de que se ejecute un subproceso.

Capturar ipor valor, std::thread([i](){ print_id(i); })es decir, es la solución.

Betsabé
fuente
2
O menos usado y no es recomendablestd::thread([=](){ print_id(i); })
Wander3r
3
El comportamiento ya está indefinido porque esta es una carrera de datos en (no atómica) icon la escritura del hilo principal y la lectura de los otros hilos.
nogal
6

Dos problemas:

  1. No tiene control sobre cuándo se ejecuta el subproceso, lo que significa que el valor de la variable ien el lambda podría no ser el esperado.

  2. La variable ies local para el ciclo y solo para el ciclo. Si el ciclo termina antes de que se ejecute uno o más subprocesos, esos subprocesos tendrán una referencia no válida a una variable cuya vida útil haya finalizado.

Puede resolver ambos problemas de manera muy simple capturando la variable i por valor en lugar de por referencia. Eso significa que cada hilo tendrá una copia del valor, y esa copia se realizará de forma única para cada hilo.

Algún tipo programador
fuente
5

Otra cosa:
no espere hasta tener siempre una secuencia ordenada: 0, 1, 2, 3, ... porque el modo de ejecución de subprocesos múltiples tiene una especificidad: indeterminismo .

El indeterminismo significa que la ejecución del mismo programa, en las mismas condiciones, da un resultado diferente.

Esto se debe al hecho de que el sistema operativo programa los subprocesos de manera diferente de una ejecución a otra dependiendo de varios parámetros: carga de la CPU, prioridad de otros procesos, posibles interrupciones del sistema, ...

Su ejemplo contiene solo 5 subprocesos, por lo que es simple, intente aumentar el número de subprocesos y, por ejemplo, suspenda la función de procesamiento, verá que el resultado puede ser diferente de una ejecución a otra.

Landstalker
fuente