4.4 使用同步操作簡化代碼(C++併發編程實戰)

4.4.1 使用期望的函數化編程

術語函數化編程(functional programming)引用於一種編程方式,這種方式中的函數結果只依賴於傳入函數的參數,並不依賴外部狀態。

函數化編程的好處,並不限於那些將純粹作爲默認方式的語言。C++是一個多範型的語言。在C++11中這種方式要比C++98簡單許多,因爲C++11支持lambda表達式,還加入了boost和TR1中的std::bind,以及自動可以自行推斷類型的自動變量。期望作爲拼圖的最後一塊,它使得函數化編程模式併發化在C++中成爲可能;一個期望在對象可以在線程間互相傳遞,並允許計算其中一個結果依賴於另外一個的結果,而非對共享數據的顯示訪問。

清單4.12快速排序——順序實現版

template<typename T>
std::list<T> sequential_quick_sort(std::list<T> input)
{
	if(input.empty())
		return input;
	
	std::list<T> result;
	result.splice(result.begin(),input,input.begin());	//1
	T const& pivot = *result.begin();	//2
	
	auto divide_point = std::partition(input.begin(),input.end(),	//3
						[&](T const& t){return t < pivot});
	
	std::list<T> lower_part;
	lower_part.splice(lower_part.begin(),input,input.begin(),divide_point);//4
	
	auto new_lower(sequential_quick_sort(std::move(lower_part)));	//5
	auto new_higher(sequential_quick_sort(std::move(input)));	//6
	
	result.splice(result.end(),new_higher);	//7
	result.splice(result.begin(),new_lower);	//8
	return result;
	

因爲使用函數化模式,所以使用期望很容易轉換爲並行的版本,如下列清單所示:

4.13快速排序——“期望”並行版

template<typename T>
std::list<T> sequential_quick_sort(std::list<T> input)
{
	if(input.empty())
		return input;
	
	std::list<T> result;
	result.splice(result.begin(),input,input.begin());	
	T const& pivot = *result.begin();	
	
	auto divide_point = std::partition(input.begin(),input.end(),	
						[&](T const& t){return t < pivot});
	
	std::list<T> lower_part;
	lower_part.splice(lower_part.begin(),input,input.begin(),divide_point);
	
	std::future<std::list<T> > new_lower(
		std::async(&partial_sort_copy<T>,std::move(lower_part)));	//1
			
	auto new_higher(partial_sort_copy(std::move(input)));	//2
	
	result.splice(result.end(),new_higher);		//3
	result.splice(result.begin(),new_lower);	//4
	return result;
	
}

你也可以寫一個spawn_task()函數對std::packaged_task和std::thread做簡單的包裝;你需要爲函數創建std::packaged_task對象,可以從這個對象獲取期望;或在線程中執行它,返回期望。雖然本身並不提供太多的好處,但是它會爲轉換成一個更復雜的實現鋪平道路,將會實現向一個隊列添加任務,而後使用線程池的方式運行它們:

template<typename F,typename A>
std::future<std::result_of<F(A&&)>::type>
	spawn_task(F&& f,A&& a)
{
	typedef std::result_of<F(A&&)>::type result_type;		//推導F(A&&)返回的類型
	std::packaged_task<result_type(A&&)> task(std::move(f));//包裝到packaged_task中
	std::future<result_type> resk(task.get_future());		//獲取future狀態
	std::thread t(std::move(resk),std::move(f));			//傳入單獨的線程
	t.detach();
	return res;
}

4.4.2 使用消息傳遞的同步操作

每個線程都有一個狀態機,當線程收到信息,它將會以某種方式更新狀態,並且可能像其他線程發出一條或者多條信息,對於消息的處理依賴於線程的初始化狀態。

如下是ATM實現的一種方式(狀態機方式):線程的每一個狀態都會等待一條可接受的信息,這條信息包含需要處理的內容。這可能讓線程過度到一種新的狀態,並且循環繼續。

如下圖展示:有狀態參與的簡單實現。在這個簡化實現中,系統等待一張卡插入。當有卡插入時,系統將會等待輸入它的PIN(密碼之類),每次輸入一個數字,用戶可以刪除最後輸入數字,當輸入完成,PIN就需要驗證。當驗證有問題,你的程序就需要終止,你要爲用戶退出卡,並且繼續等待其他卡片插入到機器中。當PIN通過,你的程序要等待用戶取消交易或者選擇取款。當用戶取消交易,你的程序就可以結束了,並返回卡片。當用戶選擇取一定量的現金,你的程序需要吐出現金和返還卡前等待銀行的確認,或顯示餘額不足,並返回卡片。

清單4.15 ATM的邏輯類的簡單實現

 


struct card_inserted
{
	std::string account;
};

class atm
{
	messaging::receiver incoming;
	messaging::sender bank;
	messaging::sender interface_hardware;
	
	void (atm::*state)();
	
	std::string account;
	std::string pin;
	
	void waiting_for_card() //1
	{
		interface_hardware.send(display_enter_card());	//2
		incoming.wait().		//3
			handle<card_inserted>(
			[&](card_inserted const& msg)	//4
			{
				account = msg.account;
				pin = "";
				interface_hardware.send(display_enter_pin());
				state = &atm::getting_pin;
			}
		);
	}
	
	void getting_pin();
public:
	void run()	//5
	{
		state = &atm::waiting_for_card;	//6
		try()
		{
			for(;;)
			{
				(this->*state)();	//7
			}
		}
		catch(messaging::close_queue const&)
		{
			
		}
	}
};

運行成員函數開始5,其將會初始化waiting_for_card 6狀態,然後反覆執行當前的狀態成員函數7。waiting_for_card函數1很簡單:它發送一條信息到接口上,讓終端顯示等待卡片信息2,之後就等待傳入一條消息進行處理3。這裏處理的消息類型只能是card_inserted類的,這裏使用lambda函數4進行處理。handle函數調用時連接到wait函數上的,當收到的信息不匹配,消息自動被丟棄,並且線程繼續等待,直到收到匹配的消息。

lambda函數自身,只是將用戶的賬號信息緩存到一個成員變量中區,並清除PIN信息,再發送一條消息到硬件接口,讓顯示的界面提示用戶輸入PIN,然後線程狀態改爲獲取PIN。當消息處理結束,狀態函數將返回,然後主循環會調用新的狀態函數7。

清單4.16 簡單ATM實現中的getting_pin狀態函數

void atm::getting_pin()
{
	incoming.wait()
		.handle<digit_pressed>(		//1
		[&](digit_pressed const& msg)
		{
			unsigned const pin_length = 4;
			pin += msg.digit;
			if(pin.length() == pin_length)
			{
				bank.send(verify_pin(account,pin,incoming));
				state = &atm::verify_pin;
			}
		}
		)
		.handle<clear_last_pressed>(	//2
		[&](clear_last_pressed const& msg)
		{
			if(!pin.empty())
			{
				pin.resize(pin.length()-1);
			}
		}
		)
		.handle<cancel_pressed>(	//3
		[&](cancel_pressed const& msg)
		{
			state = &atm::done_processing;
		}		
		);
}

這次需要處理三種消息類型,所以wait函數後面接了三個handle函數調用1,2,3.每個handle都對應的消息類型作爲模板參數,並且將消息傳入一個lambda函數中。因爲這裏的調用都被連接在一起,wait的實現知道它是等待一條digit_pressed消息,或是一條clear_last_pressed消息,亦或是一條cancel_pressed消息,其他消息類型將會被丟棄。

在一個併發系統中,這種編程方式可以極大的簡化任務的設計,因爲每一個線程都完全被獨立對待。因此在使用多線程分離關注點,需要你明確如何分配線程之間的任務。

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