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,用來等待線程執行結束,效果差不多。

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章