我可以返回std::thread吗?

3

从函数返回std::thread是否安全?

例如

std::thread getWindowThread(std::function<void()> f){
    std::thread t(f);
    return t;
}

std::function<void()> func = [](){};
std::thread winT = getWindowThread(func);
winT.join();

1
是的,这段代码有明确定义的行为,并且按照您的预期工作。您是否有任何怀疑的原因? - walnut
3个回答

0

是的,只要函数的返回值用于初始化或分配给另一个std::thread对象,它就是安全的。

std::thread析构函数的唯一前提条件是joinable()false

[thread.thread.destr]

~thread();
    作用:如果 joinable(),则调用 terminate()。否则,没有任何影响。

要使线程可 joinable(),线程必须具有不等于值初始化 ID 的 ID:

[thread.thread.member]

bool joinable() const noexcept;
    返回: get_­id() != id()

std::thread 的移动构造函数和移动赋值运算符指定,被移后的线程将具有等于值初始化 ID 的 ID,因此不可 joinable()

[thread.thread.constr]

thread(thread&& x) noexcept;
    后置条件: x.get_­id() == id() 并且 get_­id() 返回在构造开始前 x.get_­id() 的值。

[thread.thread.assign]

thread& operator=(thread&& x) noexcept;
    ...
    后置条件: x.get_­id() == id() 并且 get_­id() 返回赋值之前 x.get_­id() 的值。
    ...

由于 getWindowThread 返回一个函数局部变量,其返回值将从返回的对象进行移动构造,而由 getWindowThread 返回的值将被移动构造为 winT,因此您的示例是安全的。返回的 thread 对象不再是 joinable,可以被销毁。

然而,如果返回值用于初始化或分配给另一个std::thread对象,则其析构函数将调用std::terminate()并使您的程序崩溃。因此,我建议在您的函数中添加[[nodiscard]]属性,以确保您的编译器至少在这种危险情况下发出警告(如果您可以访问C++17功能,否则有特定于编译器的扩展来启用相同的行为):

[[nodiscard]] std::thread getWindowThread(std::function<void()> f) {
    std::thread t(f);
    return t;
}

我有一个关于gmock返回线程的后续问题,例如:std :: function <void()> lambda = {}; std :: thread wThread(lambda); EXPECT_CALL(<obj>,getWindowThread(_))。将重复返回(wThread); 这会抛出异常,因为线程无法复制。如何返回模拟? - vedika seth
@vedikaseth Google的文档在这里详细介绍了此内容(https://github.com/google/googletest/blob/master/googlemock/docs/cook_book.md#mocking-methods-that-use-move-only-types)。如果您仍然不确定如何操作,可以将其作为新问题提出。 - Miles Budnek

0

看一下std::thread的构造函数。你可以检查到std::thread的复制构造函数(和复制赋值)已被删除。请参见this了解更多关于C++中移动构造函数的知识。

当你从函数返回std::thread时,它的返回对象是通过移动构造的,因为函数返回一个具有值类别prvalue的对象,所以你的变量winT也将被移动构造。

std::thread winT = getWindowThread(func);

这个方法可以正常工作,不会导致未定义的行为。您可能会认为一旦从函数返回std::thread,它就会被销毁,并且必须释放其资源,也许其线程可能会被销毁。

但是,如前所述,创建在函数中的对象将被移动构造以构造winT对象。

详细信息: 链接表示:

如果*this有一个关联的线程(joinable() == true),则调用std::terminate()。

因此,要安全地销毁任何std::thread对象,您需要确保它不可joinablejoinable()提供了更多的见解:

因此,一个默认构造的线程是不可连接的。执行代码已经完成,但尚未加入的线程仍然被视为活动执行的线程,因此仍然可以连接。

因此,当winT的移动构造函数构造它时,它会将自己与从您的函数返回的对象交换。之后,您的函数的临时变量将被销毁,并且由于它不可连接,因此可以安全地销毁它。


0

是的,很安全。那个线程将被移动到winT。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接