异步模式是 boost asio 的最重要的贡献,所以在接下来,我们会有很多篇幅描述j基于 boost asio 异步编程的方法。
异步模式是一个相当复杂的模式。基础开发实践中,子功能通过函数封装,并通过在栈上一级级调用与返回,完成最终的逻辑功能,这种方式是最常见的,甚至直接体现在CPU的设计中。这种思维定势一直印象着我们,以至于在面对异步中的问题时,经常显得无从为力,捉襟现肘。
在异步开发中,最重要的是控制,我们一直强调程序员要对他的程序有控制力。我们要从源头,从程序模块设计的时候,就要分析可能的情况,给出有效的解决方案,并把这种控制力一直持续到开发实现,测试验证的过程中。这样,我们才不会因为一个微小的疏漏而搭上整个测试、开发团队一个周末的休息时间。
现实情况中,异步实现的多样性不可避免。因为开发人员的习惯,或者只是非常偶然的因素,在异步的实现方式上会出现很多的变化,这些变化有时候是很微小的,不仔细分析都很难说出两种实现到底有什么区别,然而对整个逻辑的影响却很大。这时候,我们不得不限制开发过程中的发散思维,以一些成文或者不成文的规范要求自己。
与很多开发规范一样,异步规范是用来约束开发行为的,以达到团队成员之间协调一致。具体来说,这些约束主要针对异步开发中,异步功能的”实现者“与”使用者“之间的调用方式,以及对接口语义的理解。
在讨论具体的规范之前,我们不妨先看看异步规范是如何制定的。实际上,规范并不是凭空而来的,它是在一定场合中,为了解决一类问题而制定的。因此,异步规范并不是只有一套,在不同的应用场合有着相适应的不同的规范。
异步规范主要分两个部分:异步调用的规范,异步回调的规范。这个不难理解,因为这正是异步工作方式的体现。每个部分又分为两个小部分,分别对异步”实现者“与”使用者“做出约定。但是,有一个特别需要注意的地方,就是异步取消的行为规范。
虽然没有在文档中特别强调,但是 boost asio 中对异步IO 有着一套严格的全局一致的规范,这也是 boost asio 有如此强大生命力的原因之一。因为有规范即意味着可控性,这在软件工程中是很重要的要求。
我们就以两大部分,四个小部分的方式,简单描述一下 boost asio 的异步规范:
- 异步调用
- 调用各个接口没有特殊限定,可以在回调中调用下一个异步,可以多线程调用
- 不要期望一定能够取消异步,即使在回调被调用之前执行取消
- 关闭 IO 对象,会取消该对象的异步操作
- 删除(析构) IO 对象,会 close 该对象
- 异步被调用
- 需要实现各个接口的线程安全
- 不能抛出异常
- 取消可以失败
- 回调调用
- 不在异步请求的调用栈中执行异步回调
- 不需要考虑调用回调时的线程安全性
- 需要考虑回调接口抛出异常
- 回调被调用
- 需要考虑回调接口的线程安全性
- 可以抛出异常
- 取消异步操作时,异步回调不能被取消
在多线程场合,boost asio 这样描述:
In general, it is safe to make concurrent use of distinct objects, but unsafe to make concurrent use of a single object. However, types such asio_service
provide a stronger guarantee that it is safe to use a single object concurrently.
通常的(意思是:对于线程安全性, boost asio 并没有什么特别的约定),在不同的对象上多线程并发请求是安全的,但是在同一个对象上的多线程并发请求不安全。(这就是“通常”的意思,因为不做特别处理的对象,就是这样)。然而,像 io_service 这样的对象是多线程并发安全的。
但是,联想到 boost asio 的 IO 对象内部保存着共享的服务单实例的 c++ 引用,那么第一条约定:在不同对象上的并发安全,其实是需要服务实现其并发安全性。
Multiple threads may call io_service::run()
to set up a pool of threads from which completion handlers may be invoked. This approach may also be used with io_service::post()
to use a means to perform any computational tasks across a thread pool.
多个线程可以调用 io_service::run() 来建立一个线程池,回调函数由这些线程来执行。这个机制可以被用在 io_service::post() 上,用来在线程池中执行某个计算任务。
换个说法就是:回调函数到底需要不需要并发安全,是异步使用者说了算,只用一个线程调用 io_service::run() 的时候,异步回调不需要考虑并发安全,除非在使用者方面还有其他线程与异步回调并发访问数据。
总的来说,boost asio 异步的使用者需要关注并发安全。这对异步功能的”提供者“来说,是一个好消息,他可以专注于异步的功能实现了。但是,作为异步”使用者“,也没有必要悲观,事情不是想象中的那么复杂,对于异步中的多线程并发,我们有着一套完成的方法学来应对它,boost asio 也提供了很多的工具类来帮助我们处理线程并发安全问题。
仅就规范而且,boost asio 在线程方面约定很简单,在后面的章节中,我们还会穿插线程并发方面的内容,现在我们回到前面的几条规范,有必要做一下进一步的解释。
首先,第一条:回调是异步的,不会在异步请求的调用栈中执行异步回调。
在某些时候,异步操作很快完成,(比如,检测到存在错误状态,不能执行该操作),这时候,一种未加思索的做法是直接调用回调函数。
这种做法,违背了 boost asio 规范,在调用栈中执行了异步回调。然而这有什么问题呢?现在我们不好回答,因为这与使用者如何使用有关。
一种情况,是程序死锁,
堆栈溢出
程序行为异常