1. 程式人生 > >c++11中多執行緒中Join函式

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