1. 程式人生 > >C++11:多執行緒與鎖

C++11:多執行緒與鎖

多執行緒是小型軟體開發必然的趨勢。C++11將多執行緒相關操作全部整合到標準庫中了,省去了某些坑庫的編譯,真是大大的方便了軟體開發。多執行緒這個庫簡單方便實用,下面給出簡單的例子

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

volatile int val;
mutex mut;

void icrement () {
	for (int i = 0; i < 100000000; i++) {
		mut.lock ();
		val++;
		mut.unlock ();
	}
}

int main (int argc, char* argv []) {
	//建立兩個執行緒
	thread t1 (icrement);
	thread t2 (icrement);
	//等待兩個執行緒執行完
	t1.join ();
	t2.join ();
	cout << val << endl;
	return 0;
}
概念有點多。首先得引入thread與mutex這兩個標頭檔案,其中thread與執行緒有關,mutex與鎖有關。然後是兩個全域性變數,由於兩個執行緒都會訪問,所以加上volatile關鍵字,代表不要對這變數進行優化,然後是mutex,這個就是鎖咯。

然後,下面的main函式中,首先建立兩個執行緒,然後等待兩個執行緒執行完,然後輸出結果。最後是increment函式,這函式迴圈一億次,不停的加鎖解鎖,控制著val變數的訪問。鎖還提供一個函式,比如這樣呼叫 mut.try_lock (); ,立即返回bool型別,true代表加鎖成功,false代表加鎖失敗。

這是最基本的情況。對於普通的加鎖解鎖操作,使用lock、unlock就夠用了。但如果分支太多,這個就不好控制了。C++11提供了一種自動鎖的機制,比如上面程式碼可以替換成這樣:

void icrement () {
	for (int i = 0; i < 100000000; i++) {
		//呼叫即加鎖,離開大括號作用域自動解鎖
		lock_guard<mutex> lock (mut);
		val++;
	}
}
自動鎖就不用刻意的解鎖了,對於多分支的情況相當方便。

除了普通的鎖之外,還有三種鎖:

recursive_mutex rm;//遞迴鎖
recursive_timed_mutex rtm;//遞迴超時鎖
timed_mutex tm;//超時鎖
遞迴鎖用在遞迴的場合,超時鎖也就是普通鎖加上超時功能,基本功能與鎖相同。

鎖方便控制程式碼邏輯,但如果只是控制訪問一個變數的話,有一種更好的選擇,那就是原子。詳見

C++11:原子操作

關於程式碼邏輯的控制,除了thread之外,還有一種,std::async,用於接收執行緒函式的返回值。關於返回值,通過thread的引用引數也可以傳遞,std::async只是讓程式碼邏輯更清晰而已。示例程式碼如下:

#include <iostream>
#include <string>
#include <future>

int main (int argc, char* argv []) {
	std::future<int> future = std::async (std::launch::async, [] () {
		std::this_thread::sleep_for (std::chrono::seconds (3));
		return 4;
	});
	std::future_status status;
	do {
		status = future.wait_for (std::chrono::seconds (1));
		if (status == std::future_status::ready) {
			std::cout << "std::future_status::ready" << std::endl;
		} else if (status == std::future_status::timeout) {
			std::cout << "std::future_status::timeout" << std::endl;
		} else if (status == std::future_status::deferred) {
			std::cout << "std::future_status::deferred" << std::endl;
		}
	} while (status != std::future_status::ready);
	std::cout << future.get () << std::endl;
	return 0;
}
使用async首先需要引入future這個標頭檔案。然後是std::async這個函式,它返回一個std::future這個模板型別,型別取決於函式的返回值。

第一個引數代表啟動方式,std::launch::async代表呼叫函式時啟動,std::launch::deferred代表建立後掛起執行緒,當執行std::future<>.get()時才執行執行緒。

第二個引數傳入函式,可以是函式地址、lambda表示式或仿函式等。如果有引數的話,可以在呼叫std::async時加引數就行了。

這個執行緒執行兩句話,sleep_for這行的意思是執行緒暫停3秒。個人感覺這語法太複雜了,還不如微軟Sleep來的簡潔。暫停執行緒之後,直接返回4。

然後 我們定義一個future的狀態,future_status,用於獲取執行緒執行結果。這兒可以直接用get(),表示等待執行緒執行完,那麼就不用future了,也跟單執行緒沒區別了,所以還是用wait_for來獲取狀態。獲取狀態後進行判斷。如果為std::future_status::ready,代表執行緒已經執行完畢;如果為std::future_status::timeout,表示等待了wait_for傳入的時間之後,執行緒還沒執行完畢,還在繼續執行;如果為std::future_status::deferred,代表執行緒還處於掛起狀態。我用的VS2013 Communicaty Update4,發現無論如何也無法處於std::future_status::deferred狀態,據說是我這個版本的bug,在VS2015中修復。個人建議,掛起執行緒這個儘量不用,呼叫get來獲取還不如直接單執行緒來的直接。

然後是future.get,用於獲取執行緒的返回值。如果執行緒並沒執行完那麼就一直等著。除此之外還有一個相似的函式,wait,用來等待執行緒執行結束,效果差不多。

以上程式碼不出所料,執行結果如下: