[C++11 并发编程] 13 使用期望等待一次性事件

C++标准库使用期望(future)来支持一次性事件的等待。要等待某种一次性事件的线程可以获取一个代表该该事件的期望。这个线程可以每隔一段事件周期性的查询这个期望。此外,这个线程也可以继续做其它的处理,直到需要等待这个一次性事件才被挂起。通过期望还可以可以传递数据。

C++标准库提供了两种期望unique future(std::future<>)和shared futures(std::shared_future<>),都声明在<future>库头文件中。std::future实例只能关联到一个事件。而多个std::shared_future可以关联到同一个事件。共享期望所关联的事件发生时,所有期望实例都将同时被唤醒,而去访问这个事件。由于期望可以关联数据,所以期望都是模版类,没有数据关联到期望时,就可以使用std::future<void>和std::shared_future<void>。虽然期望被用于线程间通信,但是期望对象并不支持同步的访问,我们需要使用mutex之类的机制,来保护不同线程对它们的访问。

假设我们有一个需要较长时间的计算操作,我们要使用它的计算结果,在需要这个计算结果之前,我们可以做一些其它的操作。我们可以启动一个新的线程来做计算。std:;thread没有直接的机制来满足我们的需求。这里可以使用std::async函数模版来实现这个功能。

std::async可以启动一个异步的线程,我们无需像使用thread一样,立即开始等待这个异步线程执行结束,std::async会返回一个期望对象,这个对象最终会用来存储异步线程的返回结果。当我们需要这个结果时,对期望对象调用wait就可以让线程阻塞,直到期望准备好。例子如下:

#include <future>
#include <iostream>
int find_the_answer_to_ltuae()
{
    return 42;
}

void do_other_stuff()
{}

int main()
{
    std::future<int> the_answer=std::async(find_the_answer_to_ltuae);
    do_other_stuff();
    std::cout<<"The answer is "<<the_answer.get()<<std::endl;
}

std::async允许我们传递额外的参数给函数。如果第一个参数是一个成员函数指针,第二个参数就是成员函数对应的对象的指针,其它的参数就是传递给成员函数的参数。否则,第二个及后续的参数就是传递给作为第一个参数的函数或callable对象的参数。

#include <string>
#include <future>

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

X x;
// 调用x->foo(42, "hello");
auto f1=std::async(&X::foo,&x,42,"hello");
// 调用tmpx.bar("goodbye"); tmpx是x的拷贝
auto f2=std::async(&X::bar,x,"goodbye");

struct Y
{
    double operator()(double);
};
Y y;
// 调用tmpy(3.141);tmpy是从Y()移动构造而来
auto f3=std::async(Y(),3.141);
// 调用y(2.718);
auto f4=std::async(std::ref(y),2.718);
X baz(X&);
// 调用baz(x);
auto f6=std::async(baz,std::ref(x));
class move_only
{
public:
    move_only();
    move_only(move_only&&);
    move_only(move_only const&) = delete;
    move_only& operator=(move_only&&);
    move_only& operator=(move_only const&) = delete;
    void operator()();
};
// 调用tmp();tmp是通过std::move(move_only)构造而来
auto f5=std::async(move_only());

默认情况下,我们需要自己决定std::async是启动一个新的线程,还是在等待期望时同步的执行。我们可以通过传递给std::async的参数来控制它。参数类型为std::launch:

  • std::launch::deferred表示函数调用被推迟到期望的wait()或get()被调用时。
  • std::launch::async表示启动新的线程来执行函数。
  • std::launch::deferred | std::launch::async则表示通过具体实现来控制这个行为。

std::async模式使用第三种行为。

// 启动新的线程
auto f6=std::async(std::launch::async, Y(), 1.2);
// 在wait()或get()被调用时执行
auto f7=std::async(std::launch::deferred, baz, std::ref(x));
// 取决于实现
auto f8=std::async(std::launch::deferred | std::launch::async,
				   baz, std::ref(x));
auto f9=std::async(baz, std::ref(x));
// 调用被推迟的函数
f7.wait();

这不是让一个std::future与一个任务实例相关联的唯一方式;你也可以将任务包装入一个std::packaged_task<>实例中,或使用std::promise<>类型模板。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-12 09:33:33

[C++11 并发编程] 13 使用期望等待一次性事件的相关文章

[C++11 并发编程] 16 在期望中保存异常

如果在异步线程中发生了异常,等待期望的线程如何才能知道并且正确的处理异常呢? 假设有如下所示的一个求平方根的函数: double square_root(double x) { if(x<0) { throw std::out_of_range("x<0"); } return sqrt(x); } 通常,如果在当前线程上下文中调用square_root(),方法如下: double y=square_root(-1); 在异步线程中调用square_root(),方法如下

并发编程 13—— 线程池 之 整体架构

Java并发编程实践 目录 并发编程 01—— ConcurrentHashMap 并发编程 02—— 阻塞队列和生产者-消费者模式 并发编程 03—— 闭锁CountDownLatch 与 栅栏CyclicBarrier 并发编程 04—— Callable和Future 并发编程 05—— CompletionService : Executor 和 BlockingQueue 并发编程 06—— 任务取消 并发编程 07—— 任务取消 之 中断 并发编程 08—— 任务取消 之 停止基于线

c/c++ 多线程 等待一次性事件 packaged_task用法

多线程 等待一次性事件 packaged_task用法 背景:不是很明白,不知道为了解决什么业务场景,感觉std::asynck可以优雅的搞定一切,一次等待性事件,为什么还有个packaged_task. 用法:和std::async一样,也能够返回std::future,通过调用get_future方法.也可以通过future得到线程的返回值. 特点: 1,是个模板类,模板类型是个方法类型,比如double(int),有一个参数,类型是int,返回值类型是double. std::packag

c/c++ 多线程 等待一次性事件 std::promise用法

多线程 等待一次性事件 std::promise用法 背景:不是很明白,不知道为了解决什么业务场景,感觉std::async可以优雅的搞定一切的一次等待性事件,为什么还有个std::promise. 用法:和std::async一样,也能够返回std::future,通过调用get_future方法.也可以通过future得到线程的返回值. 特点: 1,是个模板类,模板类型是个方法类型,比如double(int),有一个参数,类型是int,返回值类型是double. std::promise<i

c/c++ 多线程 等待一次性事件 异常处理

多线程 等待一次性事件 异常处理 背景:假设某个future在等待另一个线程结束,但是在被future等待的线程里发生了异常(throw一个异常A),这时怎么处理. 结果:假设发生了上面的场景,则在调用future的get方法时,就会得到被future等待的线程抛出的异常A. 3种情况: 1,std::async 2,std::packaged_task 3,std::promise,知道发生异常了,可以不调用set_value,而是调用set_exception(std::current_ex

C++11 并发编程基础(一):并发、并行与C++多线程

正文 C++11标准在标准库中为多线程提供了组件,这意味着使用C++编写与平台无关的多线程程序成为可能,而C++程序的可移植性也得到了有力的保证.另外,并发编程可提高应用的性能,这对对性能锱铢必较的C++程序员来说是值得关注的. 回到顶部 1. 何为并发 并发指的是两个或多个独立的活动在同一时段内发生.生活中并发的例子并不少,例如在跑步的时候你可能同时在听音乐:在看电脑显示器的同时你的手指在敲击键盘.这时我们称我们大脑并发地处理这些事件,只不过我们大脑的处理是有次重点的:有时候你会更关注你呼吸的

使用 C++11 并发编程入门

一.认识并发和并行 先将两个概念, 并发与并行 并发:同一时间段内可以交替处理多个操作: 图中整个安检系统是一个并发设计的结构.两个安检队列队首的人竞争这一个安检窗口,两个队列可能约定交替着进行安检,也可能是大家同时竞争安检窗口(通信).后一种方式可能引起冲突:因为无法同时进行两个安检操作.在逻辑上看来,这个安检窗口是同时处理这两个队列 并行:同一时刻内同时处理多个操作: 图中整个安检系统是一个并行的系统.在这里,每个队列都有自己的安检窗口,两个队列中间没有竞争关系,队列中的某个排队者只需等待队

[C++11 并发编程] 12 使用条件变量创建线程间安全的队列

之前有一节中,我们使用mutex实现了一个线程间安全的堆栈.这一节,我们使用条件变量来实现一个线程间安全的队列. 标准库中的std::queue<>的接口定义如下: template <class T, class Container = std::deque<T> > class queue { public: explicit queue(const Container&); explicit queue(Container&& = Cont

[C++11 并发编程] 14 关联任务与期望

std::packaged_task<>将期望绑定到一个函数或者可调用对象.当std::packaged_task<>对象被触发时,它将调用关联的函数和可调用对象使得期望被满足,并将返回值填入期望关联的数据之中.这个可以用于构建线程池,也可以用于任务管理(每个任务在各自的线程中执行或所有任务顺序的在一个后台线程中执行).如果一个大的操作可以被拆分为多个子任务,每个子任务就可以被放入一个std::packaged_task<>实例之中,再将这个实例交给任务调度器或者线程池