C ++ Threads внутри для цикла печати неправильные значения

19

Я пытаюсь понять многопоточность в 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

Что мне не хватает?

Ermando
источник
7
Pass iпо значению к лямбда, [i].
rafix07
1
Стоит отметить, что вы используете emplace_backстранно: emplace_backпринимает список аргументов и передает их конструктору for 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 потоков, так что все просто, попробуйте увеличить количество потоков и, например, поместив режим ожидания в функцию обработки, вы увидите, что результат может отличаться от одного выполнения к другому.

Landstalker
источник