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

When should I use std::thread::detach?

本文关键字:thread detach std 什么时候 我应该      更新时间:2023-10-16

有时我不得不使用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
}

std::thread的析构函数中,如果:,则调用std::terminate

  • 线程未连接(使用t.join()(
  • 并且也没有分离(用t.detach()(

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


当程序终止时(即main返回(,不等待在后台执行的剩余分离线程;相反,它们的执行被挂起,并且它们的线程本地对象不被破坏。

至关重要的是,这意味着这些线程的堆栈没有展开,因此一些析构函数没有执行。根据这些析构函数应该采取的行动,这可能会像程序崩溃或被杀死一样糟糕。希望操作系统能释放对文件等的锁定……但你可能已经损坏了共享内存、半写文件等。


那么,您应该使用join还是detach

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

如果您不打算等待线程完成join,那么您应该调用detach,但线程将一直运行直到完成,然后终止,而不需要spawner线程专门等待它;例如

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

CCD_ 18基本上将释放能够实现CCD_ 19所需的资源。

如果线程对象结束了它的生命,并且joindetach都没有被调用,这是一个致命的错误;在这种情况下调用CCD_ 22。

此答案旨在回答标题中的问题,而不是解释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;
}

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

线程库实际上会等待main下方的每个这样的线程,但您不应该关心它。

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

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

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

例如:

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

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

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

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

#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 3n";
        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;
}