什么时候应该使用 std: : thread: : detach?

有时我必须使用 std::thread来加速我的应用程序。我还知道 join()等待线程完成。这很容易理解,但是调用 detach()和不调用它有什么区别呢?

我认为,如果没有 detach(),线程的方法将独立地使用线程工作。

不是分离:

void Someclass::Somefunction() {
//...


std::thread t([ ] {
printf("thread called without detach");
});


//some code here
}

以分离方式呼叫:

void Someclass::Somefunction() {
//...


std::thread t([ ] {
printf("thread called with detach");
});


t.detach();


//some code here
}
121494 次浏览

如果您不打算等待线程使用 join完成,但是线程会一直运行直到完成,然后在没有派生线程专门等待的情况下终止,那么您应该调用 detach;。

std::thread(func).detach(); // It's done when it's done

detach基本上将释放实现 join所需的资源。

如果一个线程对象结束了它的生命周期,而且既没有调用 join也没有调用 detach,那么这将是一个致命的错误; 在这种情况下,将调用 terminate

当你分离线程,这意味着你不必 join()之前退出 main()

线程库实际上会等待每个这样的线程 主水道以下,但是您不应该关心它。

当你有一个任务必须在后台完成,但是你不关心它的执行时,detach()主要是有用的。这通常是一些库的情况。他们可能会悄悄地创建一个后台工作线程,然后分离它,这样你就不会注意到它了。

std::thread的析构函数中,如果:

  • 线程没有连接(与 t.join())
  • 也没有分离(与 t.detach())

因此,在执行流到达析构函数之前,应该始终使用 joindetach线程。


当程序终止(即 main返回)时,不会等待在后台执行的其余分离线程; 相反,它们的执行被暂停,它们的线程本地对象被销毁。

至关重要的是,这意味着 那堆线还没解开和一些析构函数不会被执行。根据这些销毁程序应该采取的行动,这种情况可能会像程序崩溃或被杀一样糟糕。希望操作系统会释放文件锁等等。.但是你可能损坏了共享内存,写了一半的文件等等。


那么,应该使用 join还是 detach呢?

  • 使用 join
  • 除非您需要更多的灵活性,并且愿意提供一个同步机制来等待线程完成 独自一人,在这种情况下,您可以使用 detach

根据 Cppreference.com:

将执行线程与线程对象分离,允许 独立地继续执行。任何分配的资源将 线程退出时释放。

调用分离后,*this不再拥有任何线程。

例如:

  std::thread my_thread([&](){XXXX});
my_thread.detach();

注意这个局部变量: my_thread,当 my_thread的生命周期结束时,将调用 std::thread的析构函数,并在析构函数中调用 std::terminate()

但是如果使用 detach(),就不应该再使用 my_thread,即使 my_thread的生存期结束,新线程也不会发生任何事情。

这个答案旨在回答题目中的问题,而不是解释 joindetach之间的区别。那么什么时候应该使用 std::thread::detach呢?

在适当维护的 C + + 代码中,根本不应该使用 std::thread::detach。程序员必须确保所有创建的线程优雅地退出,释放所有获得的资源,并执行其他必要的清理操作。这意味着通过调用 detach放弃线程的所有权不是一个选项,因此 join应该在所有场景中使用。

然而,有些应用程序依赖于旧的、经常设计不良的、受支持的 API,这些 API 可能包含无限期的阻塞函数。将这些函数的调用移动到专用线程中以避免阻塞其他内容是一种常见的做法。没有办法让这样的线程优雅地退出,所以使用 join只会导致主线程阻塞。这种情况下,使用 detach是一种不那么邪恶的替代方法,比如,使用动态存储持续时间分配 thread对象,然后故意泄漏它。

#include <LegacyApi.hpp>
#include <thread>


auto LegacyApiThreadEntry(void)
{
auto result{NastyBlockingFunction()};
// do something...
}


int main()
{
::std::thread legacy_api_thread{&LegacyApiThreadEntry};
// do something...
legacy_api_thread.detach();
return 0;
}

也许迭代上面的一个答案中提到的内容是个好主意: 当主函数完成并且主线程关闭时,所有产生的线程要么被终止,要么被挂起。因此,如果在主线程关闭之后,您依赖分离来让后台线程继续运行,那么您将会感到惊讶。要查看效果,请尝试以下操作。如果您取消注释最后一次休眠调用,那么输出文件将被创建并写入。否则:

#include <mutex>
#include <thread>
#include <iostream>
#include <fstream>
#include <array>
#include <chrono>


using Ms = std::chrono::milliseconds;


std::once_flag oflag;
std::mutex mx;
std::mutex printMx;
int globalCount{};
std::ofstream *logfile;
void do_one_time_task() {
//printMx.lock();
//std::cout<<"I am in thread with thread id: "<< std::this_thread::get_id() << std::endl;
//printMx.unlock();
std::call_once(oflag, [&]() {
//  std::cout << "Called once by thread: " << std::this_thread::get_id() << std::endl;
//  std::cout<<"Initialized globalCount to 3\n";
globalCount = 3;
logfile = new std::ofstream("testlog.txt");
//logfile.open("testlog.txt");
});
std::this_thread::sleep_for(Ms(100));
// some more here
for(int i=0; i<10; ++i){
mx.lock();
++globalCount;
*logfile << "thread: "<< std::this_thread::get_id() <<", globalCount = " << globalCount << std::endl;
std::this_thread::sleep_for(Ms(50));
mx.unlock();
std::this_thread::sleep_for(Ms(2));
}


std::this_thread::sleep_for(Ms(2000));
std::call_once(oflag, [&]() {
//std::cout << "Called once by thread: " << std::this_thread::get_id() << std::endl;
//std::cout << "closing logfile:\n";
logfile->close();
});


}


int main()
{
std::array<std::thread, 5> thArray;
for (int i = 0; i < 5; ++i)
thArray[i] = std::thread(do_one_time_task);


for (int i = 0; i < 5; ++i)
thArray[i].detach();


//std::this_thread::sleep_for(Ms(5000));
std::cout << "Main: globalCount = " << globalCount << std::endl;


return 0;
}