C++ std::future期值

(本文参照C++在线手册和《C++并发编程实战》)

0.了解std::future

C++11提供了类模板std::future来访问异步操作结果。可以使用future来代表特定的一次性事件,线程周期性的在这个future上检测事件是否发生,当所需的事件已发生future变为就绪状态,事件发生之后future也无法复位。标准库中有两类future,唯一future(std::future)对象是仅有的一个指向其关联事件的实例,而多个共享future(std::shared_future)对象可以指向同一共享状态。对于共享future而言,所有实例对象将同时变为就绪。最基本的一次性事件是在后台运行着的计算结果(这书把异步说的这么复杂)。

(虽然future被用于进程间通信,但是future对象本身并不提供同步访问,需要使用互斥锁或其他同步机制来保护访问。)

操作流程:

  • (通过 std::async、std::packaged_atsk 或 std::promise 创建的)异步操作能提供一个 std::future 对象给该异步操作的创建者。(如:std::future f = std::async(std::launch::async, [](){ return 8; }); )
  • 然后,异步操作的创建者能用各种方法查询、等待或从 std::future 提取值。若异步操作仍未提供值,则这些方法可能阻塞。(如:f.wait(); 或 f.get(); )
  • 异步操作准备好发送结果给创建者时,它能通过修改链接到创建者的 std::future  的共享状态进行。(如 : std::promise::set_value )

 类接口:

C++ std::future期值_第1张图片

1.配合异步std::async

可以使用std::async异步地执行一个函数,并返回一个std::future,std::future最终最终将持有函数的返回值。std::async的参数传递方式类似std::bind,此外还可以传递一个指定调用策略的参数。若设置std::launch::async标志,则函数在新的线程执行;设置std::launch::deferred则函数在调用wait()或get()时执行,该参数默认为std::launch::async|std::launch::deferred,即由具体实现来选择。并且,std::async参数中的可调用对象和给可调用对象的参数需要满足移动构造的要求。

#include 
#include 
#include 

struct X
{
	void foo(int, std::string const&){}
	std::string bar(std::string const&) { return std::string("yes"); }
};

X x;
auto f1 = std::async(&X::foo, &x, 1, "hello"); //调用obj->foo(1,"hello"),其中obj是&x
auto f2 = std::async(&X::bar, x, "goodbye"); //调用obj.bar("goodbye"),其中obj是x的副本

struct Y
{
	double operator()(double f) { return f * f; } //仿函数
};

Y y;
auto f3 = std::async(Y(), 123); //调用obj(123),其中obj是从Y()移动构造,可以自定义移动构造
auto f4 = std::async(std::ref(y), 123); //调用y(123)

X baz(X& x) { 
	std::cout << "baz runing" << std::endl;
	return x; 
}
auto f5 = std::async(baz, std::ref(x));//调用baz(x)

auto f6 = std::async(std::launch::async, Y(), 123);//在新的线程中运行
auto f7 = std::async(std::launch::deferred, baz,std::ref(x)); //在wait或get时运行 

auto f8 = std::async(std::launch::async | std::launch::deferred, 
	baz, std::ref(x));//由具体实现来抉择,同f5


int main()
{
	f7.wait();

	// 先去执行其他操作,再通过get()获取std::async操作结果
	f7.get();

	system("pause");
	return 0;
}

2.配合任务std::packaged_task

std::packaged_task包装任何可调用对象(函数、lambda表达式、bind表达式或其他函数对象),使得能异步调用它。其返回值或所抛出异常被存储于通过std::future对象访问的共享状态中。std::packaged_task对象是一个可调用对象,可以被封装入一个std::function对象,可以作为线程函数传给std::thread,或作为参数传递并回调,或者直接调用。其返回值作为异步结果,存储在由get_future()获取的std::future中。

#include 
#include 
#include 
#include 
#include 

int mul(int a,int b)
{
	return a * b;
}

//std::packaged_task包装任何可调用对象(函数、lambda表达式、bind表达式或其他函数对象)

void task_lambda()
{
	std::packaged_task task([](int a,int b) {
		return mul(a, b);
	});
	std::future result = task.get_future();
	task(2, 3); //2*3=6
	std::cout << "task_lambda:2*3=" << result.get() << std::endl;
}

void task_bind()
{
	std::packaged_task task(std::bind(mul, 2, 3));
	std::future result = task.get_future();
	task();
	std::cout << "task_bind:2*3=" << result.get() << std::endl;
}

void task_thread()
{
	std::packaged_task task(mul);
	std::future result = task.get_future();
	std::thread task_td(std::move(task), 2, 3);
	task_td.join();
	std::cout << "task_thread:2*3=" << result.get() << std::endl;
}

void task_deque()
{
	std::deque> task_q;
	std::packaged_task t1(std::bind(mul, 2, 3));
	std::packaged_task t2(std::bind(mul, 2, 4));
	std::packaged_task t3(std::bind(mul, 2, 5));
	task_q.push_back(std::move(t1));
	task_q.push_back(std::move(t2));
	task_q.push_back(std::move(t3));
	//... 
	task_q.front()();
	//...
	std::future result = task_q.front().get_future();
	task_q.pop_front();
	std::cout << "task_deque:2*3=" << result.get() << std::endl;
}

int main()
{
	task_lambda();
	task_bind();
	task_thread();
	task_deque();

	system("pause");
	return 0;
}

3.配合std::promise

std::promise提供一种设置值(类型T)的方式,它可以在这之后通过相关联的std::future对象进行读取。一对std::promise/std::future(可以通过get_future()函数获取promise相关的future对象),线程A通过设置promise的值(使用set_value()函数)使future变为就绪状态,线程B阻塞future待其就绪后可以获取所存储的值。如果销毁std::promise时未设置值,则将存入一个异常。

#include 
#include 
#include 
//数值操作库
#include  
#include 
//日期时间
#include  

void accumulate(std::vector::iterator begin
,std::vector::iterator end
,std::promise accumulate_promise)
{
	const int sum = std::accumulate(begin, end, 0); //求累加和,0为初始值
	std::this_thread::sleep_for(std::chrono::seconds(1));
	accumulate_promise.set_value(sum); //提醒future
}

void do_work(std::promise barrier)
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
	barrier.set_value();
	std::cout << "do_work end\n";
}

int main()
{
	//演示用promise在线程间传递结果
	std::vector numbers(10);//size=10
	std::iota(numbers.begin(), numbers.end(), 0);//从0开始递增填充
	std::promise accumulate_promise;
	std::future accumulate_future = accumulate_promise.get_future();
	std::thread work_thread(accumulate,numbers.begin(),numbers.end(),
		std::move(accumulate_promise));

	//future::get()将等待直至该future拥有合法结果并取得他
	//无需再get()前调用wait()
	//accumulate_future.wait(); //等待结果
	std::cout << "result=" << accumulate_future.get() << std::endl;
	work_thread.join(); //等待线程中操作结束

	//演示用promise在线程间对状态发信号
	std::promise barrier;
	std::future barrier_future = barrier.get_future();
	std::thread do_work_thread(do_work, std::move(barrier));
	barrier_future.wait();
	std::cout << "do_work_thread join\n";
	do_work_thread.join();

	system("pause");
	return 0;
}

4.保存异常

如果std::async或std::packaged_task使用过程中发生异常,异常会被存储于future中,代替所存储的值,future变为就绪状态,在调用get()时会重新引发所存储的异常。std::promise如果期望存储一个异常,则调用set_exception()而不是set_value(),

extern std::promise some_promise;

try {
	//计算value
	some_promise.set_value(value);
}
catch (...) {
	//std::current_exception()获取已引发的异常
	some_promise.set_exception(std::current_exception());
	//std::copy_exception()存储新的异常
	//some_promise.set_exception(std::copy_exception(std::logic_error("f")));
}

另一种情况是,当future还未就绪,future关联的std::promise或std::packaged_task的析构函数会将具有std::future_errc::broken_promise错误代码的std::future_error异常存储在相关联的状态中。

5.等待自多个线程

如果从多个线程访问单个std::future对象而不进行额外的同步,就会出现数据竞争和未定义的行为。并且,std::future调用一次get()后,就没有任何可获取的值留下了。

如果要求多个线程等待同一个事件,可以使用std::shared_future。std::future是可移动的,所有权可以在实例间转移,但是一次只有一个实例指向特定的异步结果。std::shared_future实例是可复制的,因此可以有多个对象引用同一个相关状态。对于单个std::shared_future实例,仍然需要锁来保护访问。如果每个线程都通过自己的std::shared_future对象去访问该状态,那么就是安全的。

参考手册上的例子,通过一个单独的promise关联shared_future,等待两个线程的任务都完成:

#include 
#include 
#include 

int main()
{
	std::promise ready_promise, t1_ready_promise, t2_ready_promise;
	std::shared_future ready_future(ready_promise.get_future());

	std::chrono::time_point start;

	auto fun1 = [&, ready_future]() -> std::chrono::duration
	{
		t1_ready_promise.set_value();
		ready_future.wait(); // 等待来自 main() 的信号
		return std::chrono::high_resolution_clock::now() - start;
	};

	auto fun2 = [&, ready_future]() -> std::chrono::duration
	{
		t2_ready_promise.set_value();
		ready_future.wait(); // 等待来自 main() 的信号
		return std::chrono::high_resolution_clock::now() - start;
	};

	auto result1 = std::async(std::launch::async, fun1);
	auto result2 = std::async(std::launch::async, fun2);

	// 等待线程变为就绪
	t1_ready_promise.get_future().wait();
	t2_ready_promise.get_future().wait();

	// 线程已就绪,开始时钟
	start = std::chrono::high_resolution_clock::now();

	// 向线程发信使之运行
	ready_promise.set_value();

	std::cout << "Thread 1 received the signal "
		<< result1.get().count() << " ms after start\n"
		<< "Thread 2 received the signal "
		<< result2.get().count() << " ms after start\n";

	system("pause");
	return 0;
}

 

你可能感兴趣的:(C++,没有结局的开始)