为什么在单独的线程中调用成员函数会导致 non-deterministic 行为?

Why does calling a member member function in a separate thread result in non-deterministic behaviour?

我将我的问题分解为可重现的问题。在下面的代码中我们有一个 class printerprinter::run 将打印 printerid_printer::spawn 将为您介绍新打印机。

main 中,我生成了 10 台打印机。创建的线程将在 main 结束时加入。我的期望是,每次我们 运行 这个(这是标题中的 "deterministic behavior" )时,我们都会按某种顺序获得从 0 到 9 的数字。不是这种情况。我们有时会得到其他数字。这是为什么?

#include <thread>
#include <iostream>
#include <vector>

class printer
{
public:
    printer(int i) : i_(i){};
    void run()
    {
        std::cout << i_ << '\n';
    }
    std::shared_ptr<std::thread> spawn()
    {
        return std::make_shared<std::thread>([=]() { run(); });
    }

private:
    int i_;
};

int main()
{
    std::vector<std::shared_ptr<std::thread>> vec;
    for (int i = 0; i < 10; ++i)
    {
        printer p{i};
        vec.push_back(p.spawn());
    }
    for (auto a : vec)
    {
        a->join();
    }
    return 0;
}

关于我的期望的一些想法:

我猜想我们在调用 spawn 时复制了当前 printer 的状态,因为我们通过值 (lambda) 捕获状态,而不是通过引用。

即使我们通过引用捕获,我们仍然将状态复制到新线程。

即使没有发生这种情况,每个线程都有自己的 printer。我错过了什么?

一些示例输出:

(为简洁起见,我将所有数字放在一行中)

1, 3, 4, 2, 5, 6, 7, 8, 9, -116618192
1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144
1, 2, 33, , 6, 6, 7, 9, 9, 1344437296
1, 43, 2, 6, , 6, 7, 8, 9, -1194894256

有些输出没有全部 10 个数字...

这是因为您在创建线程的循环中创建和销毁了一个打印机对象。当循环进入下一次迭代时,上一次迭代创建的打印机已经被销毁,你创建的引用它的线程将引用这个被销毁的对象。这是未定义的行为。

基本上您的所有线程都将使用相同的 printer 对象。在某些时候,它占用的内存在用于保存其他数据时会被覆盖。

您可能想要创建一个打印机数组,以便可以将不同的打印机传递给每个线程。

这里

{
    printer p{i};
    vec.push_back(p.spawn());
}

对象 p 的生命周期结束于 }。因此,您推入向量的 lambda 将尝试调用不再存在的对象的成员函数。您的代码有未定义的行为,看到正确的输出只是巧合。