C++(11):多线程同步promise

promise用于在多个线程间传值,多用于一个线程执行到某个状态后,把自己的信息传递给另外一个线程(该线程在拿到这个信息前,会阻塞等待):

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <functional>
using namespace std;


void init(promise<int> &p)
{
	this_thread::sleep_for(chrono::seconds(1));
	cout<<"init ok"<<endl;
	p.set_value(1);                    //对promise赋值
	this_thread::sleep_for(chrono::seconds(2));
	cout<<"init done"<<endl;
}

int main ()
{
  promise<int> pr1;                    //相当于一个对将来会被设置一个值的承诺
  future<int> fu1 = pr1.get_future();  //每个promise对象都有一个对应的future对象,future对象用于管理未来的值
  thread t1 (init, ref(pr1));
  auto status = fu1.get();             //在promise赋值前一直阻塞,promise赋值后可以那到对应的值
  cout<<"get init status:"<<status<<endl;
  t1.join();
  cout<<"t1 join"<<endl;
  return 0;
}

运行程序输出:
init ok
get init status:1
init done
t1 join

可以看到t1线程初始化到某个阶段时通过promise赋值,而主线程在promise被赋值前,会一直阻塞在future::get函数上。通过这种方式,可以很好的同步多线程直接的状态及数据。

当然也可以通过使用条件变量的方式达到同样的目的。

不过由于promise/future实际上是模板类,因此可以用于传递更为复杂的对象:

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <functional>
using namespace std;

class A{
public:
	A(int a):m_a(a)
	{
		cout<<"A Construct, this addr:"<<this<<endl;
	}
	A(const A& a):m_a(a.m_a)
	{
		cout<<"A Copy Construct, ori addr"<<&a<<" this addr:"<<this<<endl;
	}
	A(A&& a):m_a(a.m_a)
	{
		cout<<"A Move Construct, ori addr"<<&a<<" this addr:"<<this<<endl;
	}
	int m_a;
};

void init(promise<A> &p)
{
	this_thread::sleep_for(chrono::seconds(1));
	cout<<"init ok"<<endl;
	p.set_value(A(5));
}

int main ()
{
  promise<A> pr1;
  future<A> fu1 = pr1.get_future();
  thread t1 (init, ref(pr1));
  auto status = fu1.get();
  cout<<"get init status:"<<status.m_a<<endl;
  t1.join();
  cout<<"t1 join"<<endl;
  return 0;
}

运行程序输出:
init ok
A Construct, this addr:0x292fccc
A Move Construct, ori addr0x292fccc this addr:0x756fa0
A Move Construct, ori addr0x756fa0  this addr:0x67fe0c
A Move Construct, ori addr0x67fe0c  this addr:0x67fdb4
get init status:5 addr:0x67fdb4
t1 join

可见promise可以通过移动构造函数低成本的在线程间同步数据

需要指出的是:

  1. promise允许move语义(右值构造,右值赋值),不允许拷贝(拷贝构造、赋值),future亦然。
  2. 与promise关联的future只能通过promise::get_future获取,一个promise实例只能与一个future关联共享状态,当在同一个promise上反复调用get_future会抛出future_error异常。
  3. set_value只能被调用一次,多次调用会抛出std::future_error异常。
  4. 如果promise直到销毁时,都未设置过任何值,则promise会在析构时自动设置为std::future_error,这会造成future.get抛出std::future_error异常。
  5. 通过promise::set_exception函数可以设置自定义异常,该异常最终会被传递到future,并在其get函数中被抛出。

既然promise可以用于传递对象,那么是否可以传递函数对象呢?

#include <iostream>
#include <thread>
#include <future>
#include <functional>
using namespace std;

int add(int a, int b, int &res)
{
	return res=(a+b);
}

int sub(int a, int b, int &res)
{
	return res=(a-b);
}

using T_FUNC = function<int(int, int, int&)>;

template<class TF>                         
void getFunc(promise<TF> &p, int funcType) //根据不同的funcType返回不同的函数
{
	if(funcType == 1)
	{
		p.set_value(add);                  //与sub的写法都可以
	}
	else if(funcType == 2)
	{
		p.set_value(bind(sub, placeholders::_1, placeholders::_2, placeholders::_3));
	}
}

template<class T, class ...Args>
void getRes(future<T> &fu, Args&& ...args) //完美转发
{    
	auto func = fu.get();                  //获得函数对象
	func(forward<Args>(args)...);          //可变参数的完美转发       
}

int main ()
{
  int resAdd = 0, resSub = 0;
  promise<T_FUNC> pr1;
  future<T_FUNC> fu1 = pr1.get_future();
  thread t1 (getFunc<T_FUNC>, ref(pr1), 1);
  thread t2 (getRes<T_FUNC, int, int, int&>, ref(fu1), 1, 2, ref(resAdd));

  promise<T_FUNC> pr2;
  future<T_FUNC> fu2 = pr2.get_future();
  thread t3 (getFunc<T_FUNC>, ref(pr2), 2);
  thread t4 (getRes<T_FUNC, int, int, int&>, ref(fu2), 8, 6, ref(resSub));

  t1.join();
  t2.join();
  cout<<"add(1,2)="<<resAdd<<endl;
  t3.join();
  t4.join();
  cout<<"sub(8,7)="<<resSub<<endl;

  return 0;
}

运行程序输出:
add(1,2)=3
sub(8,7)=2


版权声明:本文为jiemashizhen原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。