在asio框架中,同步的io主要流程如下:
应用程序必须有一个io_service对象. io_service对象负责连接应用程序与操作系统的IO服务.
boost::asio::io_service io_service;
要执行IO操作应用程序需要一个像TCP Socket的IO对象:
boost::asio::ip::tcp::socket socket(io_service);
而后执行同步连接操作,发送如下事件:
1. 应用程序通过调用IO对象初始化连接操作:
socket.connect(server_endpoint);
2. IO对象向io_service 提出请求.
3. io_service 调用操作系统功能执行连接操作.
4. 操作系统向io_service 返回执行结果.
5. io_service将错误的操作结果翻译为boost::system::error_code类型. error_code可与特定值进行比较,或作为boolean值检测(false表示无错误).结果再传递给IO对象.
6. 如果操作失败,IO对象抛出boost::system::system_error类型的异常.开始操作的代码如下所示:
boost::system::error_code ec;
socket.connect(server_endpoint, ec);
而后error_code类型的变量ec被赋予操作的结果值,但不会抛出异常.
对于异步操作,事件顺序不同.
1. 应用程序调用IO对象进行连接操作:
socket.async_connect(server_endpoint, your_completion_handler);
your_completion_handler函数的签名为:
void your_completion_handler(const boost::system::error_code& ec);
执行的异步操作需要严格的函数签名.每种操作的合法形式可见参考文档.
2. IO对象请求io_service .
3. io_service 通知操作系统其需要开始一个异步连接.
时序过程.(在同步情况下等待包括连接操作时间.)
4. 操作系统指示连接操作完成, io_service从队列中获取操作结果.
5. 应用程序必须调用io_service::run()(或io_service相似的成员函数)以便于接收结果.调用io_service::run()会阻塞未完成的异步操作,因此可在启动第一个异步操作后调用这个函数.
6. 调用io_service::run()后,io_service返回一个操作结果,并将其翻译为error_code,传递到事件处理器中.
这是Boost.Asio的简单图形.更多特性可从文档中获取,如使用Boost.Asio执行其他类型的异步操作.
二、io_service对象
io_service对象主要有两个方法——post和run:
(1) post用于发布io事件,如timer,socket读写等,一般由asio框架相应对象调用,无需我们显式调用。
(2) run用于监听io事件响应,并执行响应回调,对于异步io操作需要在代码中显式调用,对于同步io操作则由io对象隐式调用(并不是run函数,不过也是等待io事件)。
可见,io_service提供的是一个生产者消费者模型。在异步io操作中需要我们手动控制消费者,调用run函数,它的基本工作模式如下:
(1)等待io事件响应,如果所有io事件响应完成则退出
(2)等待到io事件响应后,执行其对应的回调
(3)继续等待下一个io事件,重复(1)-(2)
从中可以看出,io_service是一个工作队列的模型。在使用过程中一般有如下几个需要注意的地方:
1. run函数在io事件完成后会退出,导致后续基于该对象的异步io任务无法执行
由于io_service并不会主动创建调度线程,需要我们手动分配,常见的方式是给其分配一个线程,然后执行run函数。但run函数在io事件完成后会退出,线程会终止,后续基于该对象的异步io任务无法得到调度。
解决这个问题的方法是通过一个asio::io_service::work对象来守护io_service。这样,即使所有io任务都执行完成,也不会退出,继续等待新的io任务。
boost::asio::io_service io;
boost::asio::io_service::work work(io);
io.run();
2. 回调在run函数的线程中同步执行,当回调处理时间较长时阻塞后续io响应
解决这个问题的方法有两种:1. 启动多线程执行run函数(run函数是线程安全的),2. 新启动一个线程(或通过线程池)来执行回调函数。一般来讲,如果回调处理事件不是特别短,应该使用在线程池中处理回调的方式。
3. 回调在run函数的线程中同步执行,io事件较多的时候得不到及时响应
这个其实是性能问题了,在多核cpu上可以通过在多个线程中执行run函数来解决这一问题。这种方式也只能充分利用cpu性能,本身性能问题就不是光靠软件就能解决的。
.net中的异步io调度方式
和io_service这种手动控制的方式比起来,.net则是纯粹的自动档了。IO调度由CLR托管了,无需手动控制。回调也是在线程池中执行,无需担心影响后续IO响应。
正是由于CLR的托管,在.net 的异步IO框架中,就没有类似io_service的调度对象存在,这也符合.net的一贯简洁做法。