c ++ Нитки всередині для циклічного друку неправильних значень


19

Я намагаюся зрозуміти Multi-Threading в c ++, але я застряг у цій проблемі: якщо я запускаю теми в циклі for, вони друкують неправильні значення. Це код:

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

Я сподівався отримати друковані значення 0,1,2,3,4, але часто отримував одне і те ж значення двічі. Це вихід:

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

Що мені не вистачає?


7
Pass iза значенням до лямбда, [i].
rafix07

1
Варто зауважити, що ваше використання emplace_backнепарне: emplace_backбере список аргументів і передає його конструктору для std::thread. Ви передали (rvalue) екземпляр std::thread, отже, побудуєте потік, а потім перемістіть цей потік у вектор. Ця операція краще виражається більш поширеним методом push_back. Було б розумніше або писати threads.emplace_back([i](){ print_id(i); });(будувати на місці), або threads.push_back(std::thread([i](){ print_id(i); }));(конструювати + рухатись), які дещо ідіоматичніші.
Міло Брандт

Відповіді:


17

[&]Синтаксис викликає iбути захоплений за допомогою посилання . Тому досить часто, iколи нитка запущена, буде просуватися далі, ніж ви могли очікувати. Більш серйозно, поведінка вашого коду не визначена, якщо вона iвиходить за межі до запуску потоку.

Захоплення iза значенням - тобто std::thread([i](){ print_id(i); })виправлення.


2
Або менш використаний і не часто доцільнийstd::thread([=](){ print_id(i); })
Wander3r

3
Поведінка вже не визначена, оскільки це перегони даних щодо (неатомного) iз написанням основного потоку та зчитуванням інших потоків.
волоський горіх

6

Дві проблеми:

  1. Ви не маєте контролю над тим, як запускається потік, а значить, значення змінної iв лямбда може бути не такою, яку ви очікуєте.

  2. Змінна iлокальна лише для циклу та циклу. Якщо цикл закінчується до запуску одного або декількох потоків, ці потоки матимуть недійсне посилання на змінну, термін служби якої закінчився.

Ви можете вирішити обидві ці проблеми дуже просто, захопивши змінну i за значенням, а не за посиланням. Це означає, що кожен потік матиме копію значення, і ця копія буде зроблена унікально для кожного потоку.


5

Інша справа:
не чекайте, поки завжди буде впорядкована послідовність: 0, 1, 2, 3, ... тому що режим багатопотокового виконання має специфіку: індетермінізм .

Індетермінізм означає, що виконання однієї програми за тих же умов дає різний результат.

Це пов’язано з тим, що ОС планує потоки потоків по-різному від одного виконання до іншого залежно від кількох параметрів: завантаження процесора, пріоритет інших процесів, можливі перебої в системі, ...

Ваш приклад містить лише 5 потоків, тому це просто, спробуйте збільшити кількість потоків, і, наприклад, помістіть сон у функцію обробки, ви побачите, що результат може відрізнятися від одного виконання до іншого.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.