Boost.Asio网络库之io_service分析

时间:2022-07-02 05:54:31

io_service概述

几乎绝大多数用到Boost.Asio的代码里都会出现这样一个类:io_service。它应该算是Asio库里的核心类了,其本质是一个任务队列,但又不仅仅是个任务队列。

基本结构

io_service是个接口类(ps:这里解释一下,这里说接口类并不是指类似java的interface类,而是指其是对下层类做了一个包装,全部功能都是调用下层类来完成)。

class io_service : private noncopyable
{
private:
typedef detail::io_service_impl impl_type;
...
}

上述代码中的impl_type即是我们所说的下层类,io_service的所有操作都是由它代为执行的。
这么说,为了实现跨平台,我们在io_service提供统一的操作接口,而不管平台的差异,impl_type的实现上分为两部分,

window系平台:win_iocp_io_service
非window系平台:task_io_service

使用宏定义根据平台来决定将哪个类作为具体执行类,见源码

#if defined(BOOST_ASIO_HAS_IOCP)
typedef class win_iocp_io_service io_service_impl;
class win_iocp_overlapped_ptr;
#else
typedef class task_io_service io_service_impl;
#endif

代码一目了然,不做赘述。
之前也学习过跨平台游戏引擎cocos2dx的部分源码,发现跨平台的开源库几乎都是类似的思想,用宏定义做到根据平台不同使用不同类来执行功能,在此之上提供接口,屏蔽平台差异。
我们这里只讨论task_io_service对应的功能。

功能

上面我们说过,io_service类的本质是一个任务队列,所谓任务队列,其实就是一个队列容器而已,其内部元素为void型的函数,我们将这些函数抽象看待为任务。(ps:既然是任务队列的模式,就当然会有锁的存在,而这个锁也被许多asio的使用者看做是眼中钉肉中刺,)
io_service提供了不少方法,在这里我们具体讨论最常用也是最核心的三个方法

run,run_one

run函数的功能是:执行队列的所有任务直到全部完成。
它应该是使用的最多的一个函数,因为当我们使用io_service注册异步事件后,需要执行run函数阻塞执行任务,否则程序可能会在任务未完成时就结束。
我们来看看其源码

std::size_t task_io_service::run(boost::system::error_code& ec)
{
ec = boost::system::error_code();
if (outstanding_work_ == 0)
{
stop();
return 0;
}

thread_info this_thread;
this_thread.private_outstanding_work = 0;
thread_call_stack::context ctx(this, this_thread);

mutex::scoped_lock lock(mutex_);

std::size_t n = 0;
for (; do_run_one(lock, this_thread, ec); lock.lock())
if (n != (std::numeric_limits<std::size_t>::max)())
++n;
return n;
}

outstanding_work_是一个原子变量,通过对它的值的修改来控制run函数是否直接结束。

run_one由名字就可想而知了,它只执行任务队列里的一个任务。
在执行任务时,如果有空闲线程,直接使用该线程去执行任务(领导者追随者模式),如果没有且监听事件的线程正在运行,则将阻塞时间变为0,即边监听边执行,待任务执行完毕在阻塞在epoll_wait上。当任务执行完后,再将监听模式改为阻塞。

poll,poll_one

这两组函数和run/run_one函数的功能完全一样,只是run/run_one函数执行是阻塞的,而poll函数是非阻塞的,所以在使用poll函数时要特别注意变量或对象的生命周期

post

stop

退出,停止工作

未完待续