c ++ Wątki wewnątrz do drukowania w pętli niepoprawne wartości

19

Próbuję zrozumieć wielowątkowość w c ++, ale utknąłem w tym problemie: jeśli uruchomię wątki w pętli for, wypiszą one nieprawidłowe wartości. To jest kod:

#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;
}

Spodziewałem się wydrukowania wartości 0,1,2,3,4, ale często otrzymywałem tę samą wartość dwukrotnie. To jest wynik:

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

Czego mi brakuje?

Ermando
źródło
7
Przechodzą iprzez wartość lambda, [i].
rafix07
1
Warto zauważyć, że twoje użycie emplace_backjest dziwne: emplace_backbierze listę argumentów i przekazuje ją konstruktorowi std::thread. Minąłeś instancję (rvalue) std::thread, dlatego zbudujesz wątek, a następnie przeniesiesz ten wątek do wektora. Operację tę lepiej wyraża bardziej popularna metoda push_back. Bardziej sensowne byłoby pisanie threads.emplace_back([i](){ print_id(i); });(konstruowanie na miejscu) lub threads.push_back(std::thread([i](){ print_id(i); }));(konstruowanie + ruch), które są nieco bardziej idiomatyczne.
Milo Brandt

Odpowiedzi:

17

[&]Składni powoduje ibyć ujęte przez odniesienie . Dlatego dość często ibędą dalej zaawansowane, gdy wątek działa, niż można się spodziewać. Mówiąc poważniej, zachowanie kodu jest niezdefiniowane, jeśli iwykracza poza zakres przed uruchomieniem wątku.

Przechwytywanie iwedług wartości - to std::thread([i](){ print_id(i); })jest poprawka.

Batszeba
źródło
2
Lub rzadziej używany i niezbyt zalecanystd::thread([=](){ print_id(i); })
Wander3r
3
Zachowanie jest już niezdefiniowane, ponieważ jest to wyścig danych w (nieatomowym) iz zapisywaniem głównego wątku i odczytywaniem pozostałych wątków.
orzech
6

Dwa problemy:

  1. Nie masz kontroli nad uruchomieniem wątku, co oznacza, że ​​wartość zmiennej iw lambda może nie być zgodna z oczekiwaniami.

  2. Zmienna ijest lokalna tylko dla pętli i dla pętli. Jeśli pętla zakończy się przed uruchomieniem jednego lub więcej wątków, wątki te będą miały niepoprawne odwołanie do zmiennej, której okres ważności dobiegł końca.

Możesz rozwiązać oba te problemy w bardzo prosty sposób, przechwytując zmienną i według wartości zamiast referencji. Oznacza to, że każdy wątek będzie miał kopię wartości, a ta kopia zostanie utworzona indywidualnie dla każdego wątku.

Jakiś koleś programisty
źródło
5

Kolejna rzecz:
nie czekaj, aż będziesz mieć zawsze uporządkowaną sekwencję: 0, 1, 2, 3, ... ponieważ tryb wykonywania wielowątkowości ma swoją specyfikę: nieokreśloność .

Indeterminizm oznacza, że ​​wykonanie tego samego programu w tych samych warunkach daje inny wynik.

Wynika to z faktu, że system operacyjny planuje wątki inaczej w zależności od wykonania, w zależności od kilku parametrów: obciążenie procesora, priorytet innych procesów, możliwe przerwy w systemie, ...

Twój przykład zawiera tylko 5 wątków, więc jest to proste, spróbuj zwiększyć liczbę wątków, a na przykład przespać funkcję przetwarzania, zobaczysz, że wynik może być różny w zależności od wykonania.

Landstalker
źródło