当 std::thread 执行 operator() 时,vector 变为空

vector becomes empty when the operator() is executed by std::thread

提问人:eniac 提问时间:2/23/2023 最后编辑:eniac 更新时间:2/23/2023 访问量:72

问:

我正在尝试创建一个线程(PrinStringManager),它又创建了几个线程(PrintEntry)(取决于传入字符串向量的元素数量)。 创建的每个 PrintEntry 线程都只打印构造函数中收到的字符串。

这只是代表我问题的一个小例子。

class PrintEntry
{
public:
    PrintEntry(std::string& name) :name_(name) {}
    ~PrintEntry() {}
    void operator()() {
        std::cout << "name: " << name_;
    }
private:

    std::string name_;
};

class PrinStringManager
{
public:
    PrinStringManager(std::vector<std::string>& vec) :vec_(vec){}
    ~PrinStringManager() {
        for (auto& t : vt_) t.join();
    }

    void operator()() {
        for (auto name : vec_)
        {
            vt_.emplace_back(PrintEntry{ name });
        }
    }

private:

    std::vector<std::string> vec_;
    std::vector<std::thread> vt_;
};


void f()
{
    std::vector<std::string> vec{ "one","two", "three" };
    PrinStringManager psm{ vec };
    std::thread t{ std::ref(psm) };
    t.detach();
} 

int main()
{
    f();
    while (true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    std::cout << "Hello World!\n";
}

它正在发生的事情是,对象 PrinStringManager 是使用 3 个字符串的向量创建的,但是当线程调用对象函数(来自 PrintStringmanager)时,向量是空的:

void operator()() {
    for (auto name : vec_) //<-- vec is empty!!
    {
        vt_.emplace_back(PrintEntry{ name });
    }
}

我注意到,当到达函数f()的作用域结束时,会调用PrinStringManager的析构函数,这应该是问题所在。

有没有办法以简单的方式克服这个问题,而无需将 PrinStringManager 设置为静态,以便它永远存在?

有没有办法在线程对象内移动 psm,以便在到达 f() 作用域的末尾时,线程内的 psm 保持原始值?

C++ 标准向量 stdthread

评论

0赞 john 2/23/2023
是的,创建 in 并将对它的引用传递给 。PrintStringManagermainf
0赞 Some programmer dude 2/23/2023
“我注意到,当到达函数 f() 的范围结束时,会调用析构函数,这应该是问题所在。”不,不是真的,因为在管理器创建的所有线程都加入之前,析构函数不会返回。PrinStringManagerPrinStringManager
2赞 Some programmer dude 2/23/2023
问题在于,您将对本地对象的引用传递给分离的线程。首先,这是一个问题,因为创建的线程可能在开始之前就已经结束了。但这是一个更大的问题,即 的生命周期可能在线程完成之前就结束了,并给你留下一个无效的引用。psmpsmtpfmt
0赞 eniac 2/23/2023
@Some程序员伙计那么,我应该使用静态 PrinStringManager psm 吗?相反?
2赞 Alan Birtles 2/23/2023
这就是使用它时的问题,它更容易错误地处理对象生存期。您正在存储对局部变量的引用,该变量在 的末尾不再存在,也是一个局部变量,因此存储对该变量的引用也不起作用thread.detach()vecf()psm

答:

4赞 Artyer 2/23/2023 #1

首先,通过添加一个移动构造函数,使你的对象可移动:

    // No implicitly declared one since you have a destructor, so bring it back
    PrinStringManager(PrinStringManager&&) = default;
    // Also the move assign is probably wanted
    PrinStringManager& operator=(PrinStringManager&&) = default;

// (And also fix `PrintEntry`, probably by removing the destructor)

然后,您可以让线程包含自己的对象,而不仅仅是对堆栈上的一个对象的引用:PrinStringManager

// The thread will hold a PrinStringManager move-constructed from psm
std::thread t{ std::move(psm) };

如果由于某种原因无法使 Movable 可用,则可以使用动态分配:PrinStringManager

void f()
{
    std::vector<std::string> vec{ "one","two", "three" };
    auto psm = std::make_unique<PrinStringManager>(vec);
    // The thread holds a pointer to `psm` which is deleted
    // when the thread finishes
    std::thread t{ [psm=std::move(psm)]{ (*psm)(); } };
    t.detach();
}

评论

0赞 eniac 2/23/2023
已经尝试过了,但编译器抱怨:error C2280: 'std::thread::thread(const std::thread &)': attempting to reference a deleted function
0赞 Artyer 2/23/2023
@eniac 事实证明,您的类是不可移动的(有关详细信息,请参阅 stackoverflow.com/q/33932824,并遵循 0/3/5 规则)
0赞 eniac 2/23/2023
是的,我可以让它移动。已经修复。谢谢。