简介
boost.asio是boost中的一个基于事件的网络库。本文将介绍asio的多线程模型。
asio有两种支持多线程的方案:方案一,开启一个线程池,每个线程独占一个io_context,并在各自的线程中运行io_context的run方法;方案二,开启一个线程池,并创建一个全局的io_context,在每个线程中调用io_context的run方法。
备注:新版本的asio使用io_context代替io_servvice。
多io_context方案
在这个多线程方案中,每个线程拥有一个io_context对象,同一个socket不会在多线程中共享,因此不需要引入同步机制。针对io型服务来说,io_context的数量应与cpu数量保持一致;针对计算型服务,请求阻塞了当前线程,当前线程将无法处理其他事件。
实现
接着我们来讲一讲这个方案的实现。
为了实现线程池中每个线程拥有一个io_context对象,我们需要先实现一个context池,然后供线程池和其他操作使用。幸运的是,asio代码库的example中提供了这样一个例子。io_context_pool类即是上文提到的context池,其申明如下:
1 | class io_context_pool : private boost::noncopyable { |
这个类中提供了三个方法:
- run方法,创建线程池并在每个线程中运行io_context的run方法
- stop方法,停用所有io_context
- get_io_context方法,使用roundrobin算法获取io_context对象
接着io_context_pool类对象作为server类的成员变量,在start_accept函数和构造函数中使用。server类的声明如下:
1 | class server : private boost::noncopyable { |
共享io_context方案
这种方案先创建一个全局的io_context对象,然后开启线程池,在每个线程中调用io_context的run方法。当出现异步事件时,io_context对象会将事件句柄交付给任意线程进行处理。这时io_context不会被某个事件阻塞,但多个线程共享事件循环可能导致socket描述符被多个线程共享,引起竞态条件,为此需要使用asio提供的strand方法来解决io问题。
实现
这个方案实现起来相对简单,不再需要实现context池,只需要实现一个线程池,并在线程池中执行io_context的run方法即可。在asio库中包含了这个例子,在线程池中运行全局的io_context的run方法。
server类的声明如下:
1 | class server : private boost::noncopyable { |
从server类的声明中可以看到,全局的io_context对象存储在io_context_变量中,并在run函数和构造函数中进行使用。
性能比较
上面讲了两种方案的实现,这一节做一个简单的性能测试。
asio的example提供的两个server实现了基本的http server,本次用于测试的客户端使用brpc的http客户端,进行测试的serer开启六个个线程。这是本次性能测试使用的代码库。
测试结果显示,这两种方案的峰值qps都在2w左右,性能差不多。
这两个server都会进行文件读写操作,一定程度上影响了测试结果,且本文使用的代码并没有进行优化,这也可能无法正确区分两种方案的性能差异。