c ++ Threads dentro para loop imprimem valores incorretos

19

Estou tentando entender o Multi-threading em c ++, mas estou preso neste problema: se eu lançar threads em um loop for, eles imprimirão valores incorretos. Este é o 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;
}

Eu esperava imprimir os valores 0,1,2,3,4, mas muitas vezes obtive o mesmo valor duas vezes. Esta é a saída:

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

O que estou perdendo?

Ermando
fonte
7
Passe ipor valor para lambda [i],.
rafix07
11
Vale a pena notar que seu uso de emplace_backé estranho: emplace_backpega uma lista de argumentos e a passa para um construtor de std::thread. Você passou por uma instância (rvalue) de std::thread, portanto, construirá um thread e depois moverá o thread para o vetor. Essa operação é melhor expressa pelo método mais comum push_back. Seria mais sensato escrever threads.emplace_back([i](){ print_id(i); });(construir no lugar) ou threads.push_back(std::thread([i](){ print_id(i); }));(construir + mover) que são um pouco mais idiomáticos.
Milo Brandt

Respostas:

17

A [&]sintaxe está causando ia captura por referência . Por isso, muitas vezes iserá mais avançado quando o thread for executado do que você poderia esperar. Mais a sério, o comportamento do seu código é indefinido se ificar fora do escopo antes da execução de um encadeamento.

Capturar ipor valor - ou seja, std::thread([i](){ print_id(i); })é a correção.

Bathsheba
fonte
2
Ou menos usado e muitas vezes desaconselhávelstd::thread([=](){ print_id(i); })
Wander3r 6/02
3
O comportamento já está indefinido porque esta é uma corrida de dados no (não atômico) icom a gravação do thread principal e os outros threads lendo.
noz
6

Dois problemas:

  1. Você não tem controle sobre quando o encadeamento é executado, o que significa que o valor da variável ino lambda pode não ser o que você espera.

  2. A variável ié local apenas para o loop e o loop. Se o loop terminar antes que um ou mais encadeamentos sejam executados, esses encadeamentos terão uma referência inválida a uma variável cuja vida útil terminou.

Você pode resolver esses dois problemas de maneira muito simples capturando a variável i por valor, em vez de por referência. Isso significa que cada segmento terá uma cópia do valor e essa cópia será feita exclusivamente para cada segmento.

Algum cara programador
fonte
5

Outra coisa:
não espere até ter sempre uma sequência ordenada: 0, 1, 2, 3, ... porque o modo de execução multithreading tem uma especificidade: indeterminismo .

Indeterminismo significa que a execução do mesmo programa, nas mesmas condições, produz um resultado diferente.

Isso ocorre porque o sistema operacional agenda os threads de maneira diferente de uma execução para outra, dependendo de vários parâmetros: carga da CPU, prioridade de outros processos, possíveis interrupções do sistema, ...

Seu exemplo contém apenas 5 threads, portanto, é simples, tente aumentar o número de threads e, por exemplo, adormeça na função de processamento, você verá que o resultado pode ser diferente de uma execução para outra.

Landstalker
fonte