写在前面
Join函数作用:
Join thread The function returns when the thread execution has completed.//直到线程完成函数才返回 |
在多线程的参数传递中说到,使用join()函数,我们需要考虑,什么时候调用join()函数,因为如果在join调用之前可能会产生中断,从而跳过此次join()函数的调用,下面就来探讨一下解决产生这种情况的方法。。。
使用异常处理
#include <iostream>
#include <thread>
#include <mutex>
struct func
{
int &i;
func(int &i_):i(i_){}
void operator()()
{
for (unsigned j = 0; j < 1000000; j++)
{
//...
}
}
};
void f()
{
int some_local_state = 0;
func my_func(some_local_state);
std::thread t(my_func);
try
{
//..
}
catch(...)
{
t.join();
throw;
}
t.join();
}
程序中使用异常处理的机制,确保即使执行过程中产生异常,程序也会执行join()函数,但是方法仍然存在问题, 因为try/catch只能捕获轻量级的错误
使用RAll方式
我们可以采用资源获取即初始化方式”(RAII,Resource Acquisition Is Initialization),即提供一个类,对此进行简单的封装,在析构函数中采用join
首先我们下来说一下RAll这种方式,资源获取即初始化(RAII, Resource Acquisition Is Initialization)是指,当你获得一个资源的时候,不管这个资源是对象、内存、文件句柄或者其它什么,你都会在一个对象的构造函数中获得它,并且在该对象的析构函数中释放它。实现这种功能的类,我们就说它采用了"资源获取即初始化(RAII)"的方式。这样的类常常被称为封装类
这种方式应用十分广泛,可以随便举一个栗子:lock_guard类模板的实现:
template<class _Mutex>
class lock_guard
{ // class with destructor that unlocks a mutex
public:
using mutex_type = _Mutex; //using作用个typedef作用相同
explicit lock_guard(_Mutex& _Mtx)
: _MyMutex(_Mtx)
{ // construct and lock 构造函数,并且将互斥量上锁
_MyMutex.lock();
}
lock_guard(_Mutex& _Mtx, adopt_lock_t) //这里的adopt_lock_t是一个结构体,其中没有任何内容
: _MyMutex(_Mtx)
{ // construct but don't lock
}
~lock_guard() _NOEXCEPT //析构函数中将次进行解锁
{ // unlock
_MyMutex.unlock();
}
lock_guard(const lock_guard&) = delete; //放置编译器自动生成默认函数,并且禁止调用
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
这样,我们可以将任意一个互斥量放入次类模板中进行修饰,然后lock_guard就可以在对象析构的时候自动进行unlock操作,防止忘记进行unlock,进而造成死锁
我们可以自己封装一个给join函数使用的类:
class thread_guard
{
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;
private:
std::thread &t;
};
将上述的类用到程序中:
#include <iostream>
#include <thread>
#include <mutex>
struct func
{
int &i;
func(int &i_):i(i_){}
void operator()()
{
for (unsigned j = 0; j < 1000000; j++)
{
//...
}
}
};
class thread_guard
{
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;
private:
std::thread &t;
};
void f()
{
int some_local_state = 0;
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
}
这样不管是在执行的最后析构thread_guard对象还是出现异常,都会执行析构函数,这样就可以保证join函数一定可以得到调用
参考书籍
《c++并发编程实战》 Anthony Williams著
http://www.cplusplus.com/reference/thread/thread/join/?kw=join