forループ内のc ++スレッドが誤った値を出力する


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が出力されることを期待していましたが、同じ値を2回取得することがよくありました。これは出力です:

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

何が欠けていますか?


7
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

2つの問題:

  1. スレッドをいつ実行するかを制御することはiできません。つまり、ラムダの変数の値が期待どおりにならない場合があります。

  2. 変数iは、ループおよびループに対してのみローカルです。1つ以上のスレッドが実行される前にループが終了した場合、それらのスレッドには、有効期間が終了した変数への無効な参照が含まれます。

参照ではなくi 値で変数をキャプチャすることにより、これら両方の問題を非常に簡単に解決できます。その手段は、各スレッドがありますコピー値を、そのコピーは、スレッドごとに一意に説明します。


5

もう1つ:
マルチスレッド実行モードには特定があるため、常に順序付けされたシーケンス(0、1、2、3、...)になるまで待たないでください。

不確定性とは、同じプログラムを同じ条件で実行すると、異なる結果が得られることを意味します。

これは、CPUの負荷、他のプロセスの優先順位、起こり得るシステムの中断などのいくつかのパラメータに応じて、OSがスレッドを実行ごとに異なる方法でスケジュールするためです。

あなたの例には5つのスレッドしか含まれていないので、単純です。スレッドの数を増やして、たとえば、処理関数にスリープを入れると、実行ごとに結果が異なる場合があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.