c++11中多线程中Join函数

写在前面

Join函数作用:

Join thread

The function returns when the thread execution has completed.//直到线程完成函数才返回

This synchronizes the moment this function returns with the completion of all the operations in the thread: This blocks the execution of the thread that calls this function until the function called on construction returns (if it hasn't yet).//这将在函数返回时与线程中所有操作的完成同步:这会阻塞调用该函数的线程的执行,直到调用该函数返回(如果它还没有返回的话)。

After a call to this function, the thread object becomes non-joinable and can be destroyed safely.

在多线程的参数传递中说到,使用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

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/85465411