c ++ Utas di dalam untuk loop mencetak nilai yang salah

19

Saya mencoba memahami Multi-threading di c ++, tapi saya terjebak dalam masalah ini: jika saya meluncurkan utas dalam untuk loop mereka mencetak nilai yang salah. Ini kodenya:

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

Saya berharap bisa mencetak nilai 0,1,2,3,4 tetapi saya sering mendapatkan nilai yang sama dua kali. Ini hasilnya:

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

Apa yang saya lewatkan?

Ermando
sumber
7
Lewati inilai ke lambda [i],.
rafix07
1
Perlu dicatat bahwa penggunaan Anda emplace_backaneh: emplace_backmengambil daftar argumen dan meneruskannya ke konstruktor std::thread. Anda telah melewati instance (rvalue) dari std::thread, maka akan membangun utas, lalu memindahkan utas itu ke vektor. Operasi itu lebih baik diungkapkan dengan metode yang lebih umum push_back. Akan lebih masuk akal baik menulis threads.emplace_back([i](){ print_id(i); });(membangun di tempat) atau threads.push_back(std::thread([i](){ print_id(i); }));(membangun + bergerak) yang agak lebih idiomatis.
Milo Brandt

Jawaban:

17

The [&]sintaks yang menyebabkan iditangkap oleh referensi . Jadi, cukup sering karena itu iakan lebih maju ketika utas berjalan dari yang Anda harapkan. Lebih serius lagi, perilaku kode Anda tidak terdefinisi jika ikeluar dari cakupan sebelum utas berjalan.

Menangkap iberdasarkan nilai - yaitu std::thread([i](){ print_id(i); })memperbaiki.

Batsyeba
sumber
2
Atau kurang digunakan dan tidak sering disarankanstd::thread([=](){ print_id(i); })
Wander3r
3
Perilaku sudah tidak terdefinisi karena ini adalah perlombaan data pada (non-atom) idengan utas utama dan pembacaan utas lainnya.
kenari
6

Dua masalah:

  1. Anda tidak memiliki kontrol atas ketika utas berjalan, yang berarti nilai variabel idi lambda mungkin tidak seperti yang Anda harapkan.

  2. Variabel ilokal untuk loop dan loop saja. Jika loop selesai sebelum satu atau lebih utas berjalan, utas tersebut akan memiliki referensi yang tidak valid ke variabel yang masa pakainya telah berakhir.

Anda dapat menyelesaikan kedua masalah ini dengan sangat sederhana dengan menangkap variabel i berdasarkan nilai alih-alih dengan referensi. Itu berarti setiap utas akan memiliki salinan nilainya, dan salinan itu akan dibuat secara unik untuk setiap utas.

Beberapa programmer Bung
sumber
5

Hal lain:
Jangan menunggu sampai selalu urutan yang terurut: 0, 1, 2, 3, ... karena mode eksekusi multithreading memiliki kekhususan: ketidakpastian .

Indeterminisme berarti bahwa pelaksanaan program yang sama, di bawah kondisi yang sama, memberikan hasil yang berbeda.

Ini disebabkan oleh kenyataan bahwa jadwal OS threads berbeda dari satu eksekusi ke yang lain tergantung pada beberapa parameter: beban CPU, prioritas proses lain, kemungkinan gangguan sistem, ...

Contoh Anda hanya berisi 5 utas, jadi sederhana, coba tambah jumlah utas, dan misalnya tidur di fungsi pemrosesan, Anda akan melihat bahwa hasilnya dapat berbeda dari satu eksekusi ke yang lain.

Landstalker
sumber