在“ C ++并发行动”一书中有一个示例:
class background_task { public: void operator() () const { do_something(); do_something_else(); } }; background_task f; std::thread my_thread(f);
后跟文本:“在这种情况下,将提供的功能对象复制到属于新创建的执行线程的存储中,然后从那里调用。因此,至关重要的是,复制的行为应与原始行为或结果相同可能不是预期的。”
有人可以向我详细解释这两个句子的意思吗?可以提供给线程对象的构造函数的其他可调用类型呢,它们不会被复制吗?如何确保“副本的行为与原始行为相同”或为什么它的行为与行为不相同? 谢谢!
答案 0 :(得分:3)
该语句将给定的示例与将普通函数void do_some_work();
作为参数传递给std::thread
之前的情况进行了对比。
std::thread my_thread(do_some_work);
功能对象background_task f
应该是可复制的。如果class background_task
由使其不可复制的内容组成,则这是不可能的。例如,如果
background_task
有一个std::mutex
,由于std::mutex
是不可复制或不可移动的,因此无法复制。请参阅 Demo 。
第二件事是,即使background_task
是可复制的,也应确保副本构造函数生成原始副本的精确副本。一个没有发生这种情况的例子是众所周知的情况,其中该类具有原始指针,并且复制构造函数不进行深度复制。
可调用void do_some_work();
也像函数对象一样被复制到线程中,但是不会遇到上述问题。
答案 1 :(得分:3)
std::thread
的所有权语义与std::unique_ptr
的所有权语义相同。尽管std::thread
实例不像std::unique_ptr
那样拥有动态对象,但它拥有资源。换句话说,每个实例负责管理执行线程。此所有权可以在实例之间转移,因为std::thread
的实例是可移动的,即使它们不是可复制的。因此,在任何时候,只有一个对象与特定的执行线程相关联,同时为程序员提供了在对象之间转移所有权的选项。
在某些情况下,我们需要将所有权移至另一个线程。假设我们要编写一个函数来创建要在后台中运行的线程。但是我们希望新线程的所有权传递回调用函数,而不是等待它完成。在另一种情况下,我们创建一个线程并将所有权传递给某个函数,该函数应等待其完成。对于这两种情况,我们都需要将所有权从一个地方转移到另一个地方。
这就是std::thread
需要支持此举的原因。 C ++标准库中的许多资源拥有类型,例如std::unique_ptr
,可以移动,但不能可复制,而std::thread
是其中之一。这意味着可以在std::thread
个实例之间移动特定执行线程的所有权。
最后,正如@ P.W所说:“函数对象background_task f
应该是可复制的。如果class background_task类包含使其无法复制的内容,则这是不可能的。”
答案 2 :(得分:-1)
这里没有什么要读的东西。
无论可调用对象是什么,都必须将其复制到线程中,否则调用它将不是线程安全的。
如果可调用对象的类型具有必须说的“意外的”复制语义(认为是一个很奇怪的书面复制构造函数),那么对于任何试图使用您的代码的人来说,这都是个坏消息。
就是这样!