【问题标题】:Why does calling a member member function in a separate thread result in non-deterministic behaviour?为什么在单独的线程中调用成员成员函数会导致不确定的行为?
【发布时间】:2020-02-12 19:12:12
【问题描述】:

我将我的问题分解为可重现的问题。在下面的代码中,我们有一个类printerprinter::run 将打印 id_printerprinter::spawn 会给你一个带有新打印机的线程。

main 中,我正在生成 10 台打印机。创建的线程将在main 的末尾加入。我的期望是每次我们运行它时我们都会以某种顺序获得从 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 个数字...

【问题讨论】:

  • 请在问题中包含实际输出。 “其他数字”表示不同的顺序或真正不同的数字,例如 42、314?
  • 好的。我会这样做的。
  • 如果你给我一个-1,你将不得不向我指出问题所在。请解释我在哪里愚蠢
  • “您必须向我指出问题所在”不,请参见此处:meta.stackexchange.com/a/325417/358287
  • 是的,我知道这不是必需的。不过,答案仍然会有所帮助

标签: c++ multithreading pointers lambda member-functions


【解决方案1】:

这里

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

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 好的。但为什么这很重要?我们不将状态复制到新线程吗?
    • @User12547645 否。一个指向状态的指针被传递给线程。
    猜你喜欢
    • 2017-05-07
    • 2014-01-19
    • 1970-01-01
    • 1970-01-01
    • 2012-04-22
    • 2016-03-15
    • 2011-01-29
    相关资源
    最近更新 更多