C++异步调用机制

DinS          Written on 2017/9/7

本文介绍c++的异步机制,建议阅读《标准库多线程编程 – 基础》,因为用到了thread,但是并不是必须的。

下面有请:闪亮的头文件<future>——你的未来不是梦

名字很亮,但是是干什么用的呢?
简单说就是异步获取结果值:进程调用了一个计算,然后干别的去了,过了一会儿取得计算的值。这种情况下你可以使用线程来做,没问题,但是会增加复杂度。
使用future则是另一番景象。更进一步说,你可以不考虑开辟线程的事,而是使用future帮你管理。

一、future基础用法

先来看一个最简单的future使用例子

点击这里看大图

运行结果:

刚启动的画面:

5秒钟之后:

下面解释一下发生了什么事情。
函数DoSomeCalc没什么好解释的,就是模拟进行一个复杂的计算任务。看看main,第一句代码有两处重点:
第一个是等号前半段,我们建立了一个future对象,而且注意future是一个模板。可以理解为在未来获取某个类型的值的容器。
第二个是等号后半段,有一个async函数,这个是asynchronous(异步)的缩写。顾名思义,就是启动一个异步任务。参数很像启动一个线程的形式,实际上将其理解为启动一个线程也未尝不可。
于是整句话的语义就是我们建立了一个future,启动了一个异步任务,并将二者绑定。

之后DoSomeCalc和main开始异步执行,这里使用了等待一段时间模拟工作量。
这个异步直到遇到futTest.get()发生了一些变化。
调用future的get顾名思义就是要获取异步任务的值,然而此时DoSomeCalc并没有执行完,于是main被阻塞,直到futTest获取到值后才继续往下走。

整个程序的执行大概是这样一个概念:

如果不想一直阻塞可以使用wait_for和wait_until。用法与线程类似,比如如下代码:

点这里看大图

wait_for在等待一段时间后会返回结果,结果定义在future_status里,一般有用的就是ready和timeout。
更常见的情况是使用while不断去判定,如果是timeout再干点别的,等while跳出了说明是ready了, 就可以直接get了。
在这里例子里输出如下:

肯定是超时的。

另外还有一个shared_future,用法跟future一模一样,唯一区别是shared_future可以多次get,而future只能get一次。

二、future进阶用法

不只有async可以建立异步任务,下面介绍其他方法。
如果说future这个名字很有喜感,那么接下来的就更神奇了。

“说好的…两个人的未来呢?”

没错。下面有请promise出场:
promise这个名字槽点太多,自行脑补。还是看看能干什么吧。promise的用意是在异步的线程中提供一个便捷的同步机制。

来看下面一段故事:

这个故事是这样的:
男孩向女孩承诺将来会娶她           promise与future绑定
然后两人各奔东西                              开辟了两个线程
女孩一直在等男孩的消息                fut.get()阻塞
男孩通过5年时间打拼                      sleep_for
实现了承诺                                            prom.set
女孩终于等到了男孩                          fut.get()通过
最后是happy end!

输出结果如下:

一开始:

5秒后:

解释一下,我们定义了一个future和一个promise,并绑定起来,利用这一对实现了异步线程之间的通信和同步。
关于那个ref,就是按引用传参的意思,不过这里不能用&,据此推测开辟线程时的构造函数中使用了bind,具体可以不用深究,按这个套路写即可。

还有一个常用的是set_value_at_thread_exit,当线程结束时promise才设置值。

点评一下future和promise:
这种一对的用法是为了实现线程间同步,然而我们知道条件变量也可以达到同样的效果,那么为什么还要用这个呢?
答案是在某些情况下这种一对的用法更便捷。一个get和一个set相呼应,很简单地完成了线程间同步和传递值。如果使用条件变量,三件套肯定要上,代码逻辑复杂,并且效率还会降低,所以如果能够用future和promise完成任务,就不需要用到条件变量了。

除了promise外还有一个packaged_task,也能够提供异步任务,与promise的区别是packaged_task包装函数进去,不过感觉这个packaged_task用处不大,这里就不介绍了。

三、小结

c++的future提供了处理异步任务的独特视角。
多线程本身就是用于处理异步任务的,future实现的东西都可以用多线程来做,那么为什么需要future呢?答案是future处理的情况太常见,而用thread来做太繁琐,于是就有了简化的需求。
future处理的这个常见情况是什么?就是异步获得一个结果。
本文中这个常见情况涵盖了两种类型:
一个是async启动的异步任务,这个完全就是为了在之后获得一个结果。
另一个是future和promise配对使用,虽然带有线程间同步的作用,但是目的仍然是在未来某个时候获得一个结果。
如果发现要处理的问题带有这种性质,就可以选择future来解决。

四、全面总结

最后来一次全面的总结

首先就c++多线程的使用推荐顺序而言,
如果要解决的问题是异步获得结果,那么用future+async
如果不是,说明是更加复杂的线程问题,则使用thread
用到了thread一般都避免不了线程间通信问题,依次考虑:
如果只是在改更某个全局变量,使用atomic
如果是互斥问题,那么使用unique_lock,最好不要直接操作mutex
如果是同步问题,先看看可不可以使用future+promise解决
如果不行,那么只能请出条件变量,搭建三件套框架,通常还要用到lambda

其次跟windows多线程做一次全方位的比较
个人认为,综合而言c++的多线程更加人性化,即代码更加直观、易于理解
在开辟线程方面c++无疑更加简单,尤其是传参上绝对比windows用void*要好使、直观得多
在互斥问题上,c++与windows在功效上一致,但是c++不需要初始化和销毁对象,虽然unique_lock初看有些费解,但实际上要比windows提供的互斥好用
在同步问题上,windows更胜一筹,信号量无疑比条件变量三件套要容易使用。不过c++的条件变量+lambda可以更加灵活处理一些同步问题
c++和windows在其他问题上各有所长。得益于操作系统,windows可以提供内核变量跨进程使用,并且提供了读写锁简化代码
c++提供了future,让某些异步任务更加简单,不需要程序员去管理线程问题
另外c++在语言层面提供的移植性绝对是重要的吸引力

综合而言,我推荐首选c++完成多线程操作。

 

参考资料:http://www.cnblogs.com/haippy/p/3284540.html