asio多线程模型IOServicePool

1.简介

为了提升网络io并发处理的效率,总体来说asio有两个多线程模型,第一个是启动多个线程,每个线程管理一个iocontext。第二种是只启动一个iocontext,被多个线程共享。

第一种模式:多个线程,每个线程管理独立的iocontext服务。

设计的IOServicePool类型的多线程模型如下:

image.png

IOServicePool多线程模式特点

1   每一个io_context跑在不同的线程里,所以同一个socket会被注册在同一个io_context里,它的回调函数也会被单独的一个线程回调,那么对于同一个socket,他的回调函数每次触发都是在同一个线程里,就不会有线程安全问题,网络io层面上的并发是线程安全的。

2   但是对于不同的socket,回调函数的触发可能是同一个线程(两个socket被分配到同一个io_context),也可能不是同一个线程(两个socket被分配到不同的io_context里)。所以如果两个socket对应的上层逻辑处理,如果有交互或者访问共享区,会存在线程安全问题。比如socket1代表玩家1,socket2代表玩家2,玩家1和玩家2在逻辑层存在交互,比如两个玩家都在做工会任务,他们属于同一个工会,工会积分的增加就是共享区的数据,需要保证线程安全。可以通过加锁或者逻辑队列的方式解决安全问题,我们目前采取了后者。

3   多线程相比单线程,极大的提高了并发能力,因为单线程仅有一个io_context服务用来监听读写事件,就绪后回调函数在一个线程里串行调用, 如果一个回调函数的调用时间较长肯定会影响后续的函数调用,毕竟是穿行调用。而采用多线程方式,可以在一定程度上减少前一个逻辑调用影响下一个调用的情况,比如两个socket被部署到不同的iocontext上,但是当两个socket部署到同一个iocontext上时仍然存在调用时间影响的问题。不过我们已经通过逻辑队列的方式将网络线程和逻辑线程解耦合了,不会出现前一个调用时间影响下一个回调触发的问题。

IOServicePool实现

IOServicePool本质上是一个线程池,基本功能就是根据构造函数传入的数量创建n个线程和iocontext,然后每个线程跑一个iocontext,这样就可以并发处理不同iocontext读写事件了。

 

js

复制代码

class AsioIOServicePool:public Singleton<AsioIOServicePool> { friend Singleton<AsioIOServicePool>; public: using IOService = boost::asio::io_context; using Work = boost::asio::io_context::work;防止io_context没有注册事件时退出 using WorkPtr = std::unique_ptr<Work>;保证Work不被拷贝,只能移动或者从头用到尾 ~AsioIOServicePool(); AsioIOServicePool(const AsioIOServicePool&) = delete; AsioIOServicePool& operator=(const AsioIOServicePool&) = delete; // 使用 round-robin 的方式返回一个 io_service boost::asio::io_context& GetIOService(); void Stop(); private: AsioIOServicePool(std::size_t size = std::thread::hardware_concurrency());返回cpu核数 std::vector<IOService> _ioServices; std::vector<WorkPtr> _works; std::vector<std::thread> _threads; std::size_t _nextIOService; };

个别用法:unique_ptrboost::asio::io_context::work.reset():能够释放指针,然后对象析构

解释:

1   是一个IOService的vector变量,用来存储初始化的多个IOService。_ioServices

2   是类型的unique指针。
在实际使用中,我们通常会将一些异步操作提交给进行处理,然后该操作会被异步执行,而不会立即返回结果。如果没有其他任务需要执行,那么就会停止工作,导致所有正在进行的异步操作都被取消。这时,我们需要使用对象来防止停止工作。WorkPtr``boost::asio::io_context::work``io_context``io_context``boost::asio::io_context::work``io_context

boost::asio::io_context::work的作用是持有一个指向的引用,并通过创建一个“工作”项来保证不会停止工作,直到work对象被销毁或者调用方法为止。当所有异步操作完成后,程序可以使用方法来释放,从而让其正常退出。io_context``io_context``reset()``work.reset()``io_context

3   是一个线程vector,管理我们开辟的所有线程。_threads

4   是一个轮询索引,我们用最简单的轮询算法为每个新创建的连接分配io_context._nextIOService

5   因为IOServicePool不允许被copy构造,所以我们将其拷贝构造和拷贝复制函数置为delete

构造函数实现:

 

js

复制代码

AsioIOServicePool::AsioIOServicePool(std::size_t size):_ioServices(size), _works(size), _nextIOService(0){ for (std::size_t i = 0; i < size; ++i) { _works[i] = std::unique_ptr<Work>(new Work(_ioServices[i])); } //遍历多个ioservice,创建多个线程,每个线程内部启动ioservice for (std::size_t i = 0; i < _ioServices.size(); ++i) { _threads.emplace_back([this, i]() { _ioServices[i].run(); }); } }

_works是unique_ptr的vector类型,所以初始化时要么放在构造函数初始化列表里初始化,要么通过一个临时的右值初始化,我们采取的是第二种。std::unique_ptr

实现获取的函数io_context&

 

js

复制代码

boost::asio::io_context& AsioIOServicePool::GetIOService() { auto& service = _ioServices[_nextIOService++]; if (_nextIOService == _ioServices.size()) { _nextIOService = 0; } return service; }

我们根据作为索引,轮询获取。_nextIOService``io_context&

同样我们要实现Stop函数,控制停止的行为。因为我们要保证每个线程安全退出后再让停止。AsioIOServicePool``AsioIOServicePool

 

js

复制代码

void AsioIOServicePool::Stop(){ for (auto& work : _works) { work.reset(); } for (auto& t : _threads) { t.join(); } }

其中是让unique指针置空并释放,那么work的析构函数就会被调用,work被析构,其管理的io_service在没有事件监听时就会被释放。work.reset()

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值