QQ交流群:198941541
多io_context比单io_context来说,基本上在所有情况下都有性能优势(当然你还是应该在自己的实际生产环境下面做一些调优,以找到最优的io_context数量加service线程数量),多io_context有一些自身特定的要求,罗列如下:
一:需要定义ASCS_AVOID_AUTO_STOP_SERVICE宏(但ascs没办法做静态代码检测,所以你不定义也编译得过),这是因为,拿服务端举例,一个servcer_base只包涵一个acceptor,一个acceptor只有可能绑定到一个io_context上,那么在服务器刚启动的时候,就只有在这个io_context上才有异步操作(async_accept),其它io_context上将暂时不会有任何异步操作,那么run它们的service线程就会自动结束,这是asio的设计。
二:service线程数量必须大于等于io_context的数量,否则有些io_context就会得不到run。
三:尽量早点调用service_pump::set_io_context_num函数设置io_context数量,拿multi_client举例,如果你在add_socket之后才调用set_io_context_num,那么之前添加的socket,都将会绑定到同一个io_context上,等于是降级成了单io_context,相信这不是你所期望的。
四:service_pump::set_io_context_num不是必须要调用的,ascs会默认创建一个io_context,对于service_pump的实现来说,一个io_context和多个io_context并无区别(仅仅稍微优化了一下单io_context),这也从侧面说明了,service_pump的实现在1.6版本里几乎全部被重写了。
五:在运行时增加service线程(service::add_service_thread)的同时,也可以增加io_context,这同样要求service线程数量(这次添加的)大于等于io_context数量(这次添加的)。假如已经有了10个service线程run了5个io_context,再次添加的时候,可否加1个service线程和2个io_context呢?相当于总共11个service线程run了7个io_context,其实这是不行的(但在start_service之前确实是可行的),因为要想让前面的10个service线程退出一个来run一个新的io_context,在代码设计上是非常困难的,而且这样也带来不了多少好处。
六:如果定义了宏ASCS_DECREASE_THREAD_AT_RUNTIME,将不允许使用多io_context(代码静态检测,service_pump::set_io_context_num函数将不再提供),原因是我很难控制让指定的service线程退出来,有可能多次退出来的都是run在同一个io_context上的service线程,那么最后虽然service线程数量是大于等于io_contex数量的,但就是有些io_context没有service线程在run它们。
七:如何平均地分配多io_context给不同的socket?ascs会为每个io_context记录一个引用计数,每次创建socket的时候,都会找到拥有最小引用计数的那个io_context给这个新的socket(然后把引用计数加1),当连接断开之后,ascs会把其关联的io_context上的引用计数减1(在on_close里面,on_close专门用于资源清理,所以再次强调,重写了on_close的话,一定要在最后调用父类的on_close)以释放引用计数,到这里一切正常,问题出在socket被重用之后,ascs会在之前那个io_context上把引用计数加1(socket也的确是绑定在那个io_context上),这就有可能造成io_context引用不平均,比如最开始有5个io_context,每个io_context上面创建了100个socket,如果某个io_context上的100条连接全部断了,此时它的引用计数是0,然后又来50条新连接,由于某种原因,断了的那100个socket还不能被重用,于是新创建50个socket且都会被绑定到刚才那个io_context上,这样这个io_context上引用计数变成50,要命的是然后又来了100条连接,且原来断掉的那100个socket可以被重用了,这样一来,这个io_context上将会有150个socket,显然就不平衡了(其它的都是100个),如果想要解决这个缺陷,就只能关闭对象重用功能(如果你的对象创建需要很大的工作量,可以用对象恢复功能来代替,也可以达到引用平衡),这样每次需要新连接的时候,都是新建socket,新建的时候总是会绑定到引用计数最小的那个io_context之上,所以引用就平衡了。
八:io_context引用计数的分配粒度(均可手动修改以实现下面第九点所描述的功能):
1. 每个socket(包括所有类型的socket)对象默认都会增加一个引用计数,由timer对象引入并实现(它们都间接继承自timer对象);
2. 每个server,multi_client和multi_socket_service对象也都会增加一个引用计数,由timer对象引入并实现(它们都间接继承自timer对象);
3. 每个server对象还会额外再增加一个引用计数,由asio的acceptor引入,server实现。
总结一下就是,对于包涵100个socket的server(包括还没有accept成功的socket),它总共会引入102个io_context引用计数,假设我们总共有8个io_context,那么这102个引用计数将被平均分配到这8个io_context之上;对于包涵100个socket的multi_client或者multi_socket_service(包括还没有connect成功的socket),它总共会引入101个io_context引用计数。
九:如何故意让io_context分配不平均?比如你的某些socket上的业务非常多,而有些socket上的业务非常少,此时如果还平均分配io_context,那么可能会造成某些io_context没什么事干,而有些又太忙。解决这个问题的思路是有意的增加业务非常多的socket所绑定到的那个io_context的引用计数,具体来说:
server (acceptor):
你随时可以调用server的add/sub_io_context_refs函数来增减acceptor使用的那个io_context上的引用计数,线程安全的。注意sub_io_context_refs / clear_io_context_refs函数并不是必须,对象析构时,框架会自动调用它,只有当运行时需要减少引用计数时你才需要显式调用,下面还会介绍其它各种对象,它们也有sub_io_context_refs / clear_io_context_refs函数,使用原则也一样。另外注意一点是,当stop_listen之后,引用计数不会被退回,这倒不是遇到了什么技术难题,考虑到需要stop_listen的情况很少,我故意做成了这个样子。
server (timer) / multi_client / multi_socket_service:
如果想控制server的定时器对象上的引用计数,则需要先把server转成timer对象,如:((timer<executor>&) echo_server_).add_io_context_refs(1),也可参看echo_server;对于multi_client / multi_socket_service,则直接调用相应函数,注意它们是非线程安全的。
server_socket:
//服务端socket在创建时并不知道其用途(这意味着不能在创建/重用socket时增加
//io_context引用计数),一般来说,通过客户端发送注册消息之后,才知道这个socket的
//用途,那么可以在消息处理函数里面调用add_io_context_refs,或者本对象上的on_xxxx
//回调里面都行,否则会有多线程问题,如果非要在工作线程里面调用,可以调用socket的
//post或者set_timer函数发一个异步事件,然后在异步事件回调里面调用。
virtual void on_msg_handle(out_msg_type& msg)
{
if (need adding additional io_context references)
this->add_io_context_refs(3); //创建socket时已经增加了1个引用计数,共4个
//handle the msg
...
}
//注:你仅需手动添加引用,连接关闭之后,框架会自动退回相应的引用数量,当连接被重用之后,
//你需要重新增加引用计数,因为只有你才知道被重用之后的连接(已经是一条全新的连接了,所有
//的数据和状态将被重置,包括保存的引用计数)需要增加多少引用计数。
client_socket / UDP socket:
//客户端在创建socket的时候,就已经知道它的用途了,所以就可以增加io_context引用计数了,
//这里说的创建也包括了对象重用,所以我们应该在add_socket里面增加,而不应该在构造函数里面
//(对象重用时不会调用其构造函数)。
void main()
{
ascs::service_pump sp;
ascs::udp::multi_socket_service_base<your_reliable_socket> ms(sp);
//这里以可靠UDP为例,普通UDP,TCP和SSL也一样。
//注意ascs在创建/重用socket对象的时候已经增加了1个引用计数了,这里再3个,总共4个
//这里的3可不是接连创建3个,而是额外再增加3个io_context引用计数
//add_socket可能是新创建socket,也可以是重用socket
ms.add_socket(5050, "127.0.0.1", 3); //返回非空shared_ptr才会真正增加3个引用计数
...
}
//注:你仅需手动添加引用,连接关闭之后,框架会自动退回相应的引用数量,当连接被重用之后,
//你需要重新增加引用计数,因为只有你才知道被重用之后的连接(已经是一条全新的连接了,所有
//的数据和状态将被重置,包括保存的引用计数)需要增加多少引用计数。
十:对于TCP客户端连接,如果开启了断线重连,则断线之后,引用计数不会被退回。
上一篇 ascs 简明开发教程(20) 下一篇 ascs 简明开发教程(22)