异步函数产生不一致的结果

Dyl*_*kin 2 c++ multithreading future c++11 stdasync

我有一个异步运行的函数.不幸的是,它只会偶尔吐出正确的答案.futures[i].get()每次运行代码时由更改表示的值.我是多线程的新手.

double async_func() const {

    vector<future<double>> futures;

    double val = 0;
    for (int i = 0; i < rows; i++) {
        futures.push_back(std::async(std::launch::async, [&] {return minor(i,0).determinant();}) );
    }       
    for (int i = 0; i < rows; i++) 
        val += futures[i].get();

    return val;
}
Run Code Online (Sandbox Code Playgroud)

ste*_*fan 6

问题在于通过引用捕获.

for (int i = 0; i < rows; i++)
{
   futures.push_back(std::async(std::launch::async, [&] // capture by reference!
   {
       return minor(i,0).determinant();
   }));
}
Run Code Online (Sandbox Code Playgroud)

这是一个问题,因为每个执行的方法都没有获得自己的值i,而是对循环变量的引用i.因此,只要i对函数调用求值minor(i, 0),它就可能已经改变了.更糟糕的是,函数调用可能还没有在循环结束之前执行.因此i可能在使用之前就被销毁了.


如果以上描述不够,也许这个时间表可以提供帮助.请注意,这不是一个真实的情况,它只是一个例子.

假设迭代i需要2个时间步,评估函数的参数需要2个步骤,并且启动一个线程需要5个步骤(在真正的机器上可能更多!).每个线程完成的工作可能需要几千步(这就是我们将它们并行化的原因,对吧?).

Time -->
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^  ^  ^  ^  ^
| | | | | | | | | | |  |  |  |  Thread 3 function call.
| | | | | | | | | | |  |  |  Thread 3 evaluate parameters (i == garbage!).
| | | | | | | | | | |  |  Thread 3 start; Thread 2 function call.
| | | | | | | | | | |  Thread 2 evaluate parameters (i == garbage!).
| | | | | | | | | | Destroy i; Thread 2 start; Thread 1 function call.
| | | | | | | | | Loop condition broken; Thread 1 evaluate parameters (i == 4 !).
| | | | | | | | Thread 0 function call; Thread 1 start; i = 4;
| | | | | | | Thread 3 request to start; Thread 0 evaluate parameters (i == 3 !).
| | | | | | Thread 0 start; i = 3;
| | | | | Thread 2 request to start.
| | | | i = 2.
| | | Thread 1 request to start.
| | i = 1.
| Thread 0 request to start.
Create i, i = 0.
Run Code Online (Sandbox Code Playgroud)

所以一些线程得到有意义的数据,有些则不会.根据实际时序,可能会i在评估函数参数的值的同时递增,在这种情况下您会遇到竞争条件.


决议是i按价值捕获:

for (int i = 0; i < rows; i++)
{
   futures.push_back(std::async(std::launch::async, [=] // capture by value
   {
       return minor(i,0).determinant();
   }));
}
Run Code Online (Sandbox Code Playgroud)

建议:在处理异步位时,要明确你的捕获内容:

for (int i = 0; i < rows; i++)
{
   futures.push_back(std::async(std::launch::async, [i] // explicit capture by value
   {
       return minor(i,0).determinant();
   }));
}
Run Code Online (Sandbox Code Playgroud)

你也应该避免重新分配你的期货.只需futures.reserve(rows)在循环之前添加.