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消息,其他消息类型将会被丢弃。

在一个并发系统中,这种编程方式可以极大的简化任务的设计,因为每一个线程都完全被独立对待。因此在使用多线程分离关注点,需要你明确如何分配线程之间的任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值