C++-线程的join和detach

线程管理基础

启动线程

每个程序至少有一个线程:执行main()函数的线程,其余线程有其各自的入口函数。线程与原始线程(以main()为入口函数的线程)同时运行。
使用C++线程库启动线程,可以归结为构造 std::thread 对象,其构造函数传入的参数是可调用对象。

void do_some_work();
std::thread my_thread(do_some_work);

C++'s most vexing parse

需要注意一个关于C++语法解析的问题:“C++’s most vexing parse”

class background_task
{
public:
 void operator()() const
 {
 do_something();
 do_something_else();
 }
};
background_task f;
std::thread my_thread(f);

这里我们构造了一个函数对象f,并传入thread的构造函数中。如果我们直接这么写

std::thread my_thread(background_task());

我们本意是希望使用background_task的构造函数返回一个background对象,并直接用这个临时对象构造一个thread对象。但是在C++中,上面的表达式会被解析为:声明了一个my_thread函数,返回值为thread对象,参数为一个函数指针,这个函数指针指向的函数没有参数,且其返回值background_task对象。
为了避免这种歧义发生,除了提前声明一个函数对象之外,还可以:

  1. 新增一对括号
std::thread my_thread((background_task()));
  1. 使用初始化语法
std::thread my_thread{background_task()};

join或detach

thread对象构造完成(线程开始执行)之后,对象析构之前,我们必须选择是等待它(join)或者让它在后台运行(detach),如果你在thread对象析构前没有这么做,那么线程将会终止,因为thread的析构函数中调用了std::terminate()

  • detach使得即使thread对象析构,线程也能继续运行,但是注意,我们要确保线程结束之前它所访问的数据都是有效的。
  • 调用thread对象的join方法,函数将等待该线程完成,然后继续执行后续语句。join将清理线程相关的存储空间

thread对象只能join或detach一次,调用过join或detach的对象再调用joinable将返回false

在发生异常的情况下join

在使用detach的时候,我们通常在构造完thread对象就立即调用detach了;而join的位置则会选在thread对象析构之前的某个位置,如果在join之前发生了异常,函数将终止,join不会被调用。为了避免这种情况发生,我们可以加上try-catch语句,并且通常来说,我们希望即使发生异常也调用join方法等待。

struct func; 
void f()
{
    int some_local_state=0;
    func my_func(some_local_state);
    std::thread t(my_func);
    try
    {
        do_something_in_current_thread();
    }
    catch(...)
    {
        t.join();
        throw;
    }
    t.join();
}

这种写法过于冗长,我们的目标本质上是希望线程完成之后函数再退出,有一种简洁的方式可以达到这个目的:资源获取就是初始化(RAII,Resource Acquisition Is Initialization)

class thread_guard
{
    std::thread &t;
public:
    explicit thread_guard(std::thread &t_) : t(t_){}
    ~thread_guard()
    {
        if (t.joinable())
        {
            t.join();
        }
    }
    thread_guard(thread_guard const &) = delete;
    thread_guard &operator=(thread_guard const &) = delete;
};
struct func;
void f()
{
    int some_local_state = 0;
    func my_func(some_local_state);
    std::thread t(my_func);
    thread_guard g(t);
    do_something_in_current_thread();
}

当f函数执行结束时,局部变量析构的顺序与构造顺序相反,即先析构g,再析构t,在thread_gurad的析构函数中调用了join函数,即使do_something_in_current_thread发生异常,join函数也会被调用(实际并不是??)。因为线程只能被join一次,因此要先判断是否joinable。
这里删除了默认的拷贝构造函数和拷贝赋值函数,这是因为拷贝后的对象的生命周期可能会超出thread对象的作用域(例如函数返回一个thread_guard对象)

detach

一旦thread对象调用了detach,线程与thread对象将不再有关联,我们也没有直接的方式与线程通信,也不再能join或detach该线程,此时线程的所有权属于C++运行时库,它保证在线程退出时相关资源被回收。分离的线程通常称为守护进程,它们通常在程序的整个生命周期运行,做一些监控、清理工作。同样的thread对象只能被detach一次

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mrbone11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值