ascs 简明开发教程(十七):再谈对象生命周期管理

QQ交流群:198941541

这次我们再进行一次深入的对象生命周期管理的探讨(这里的对象,其实就是socket),对象生命周期管理是object_pool做的,所以single_client是没有生命周期管理的,那是开发者的事。

object_pool用shared_ptr管理对象,那第一感觉是不需要什么生命周期管理了,因为shared_ptr就是做这个的,但ascs在发起异步调用时,并没有把关联对象用shared_ptr包装并传给回调函数对象(而传的是this指针),这会造成当回调函数被调用时,关联对象可能已经被释放了,这显然就是个段错误,进程就崩掉了。解决这个问题的一个方法是像asio的例子那样 (cpp11/chart/chat_server.cpp):

  void do_read_body()
  {
    auto self(shared_from_this()); //增加引用记数
    asio::async_read(socket_,
        asio::buffer(read_msg_.body(), read_msg_.body_length()),
        [this, self](std::error_code ec, std::size_t /*length*/) //传入回调函数对象,防止对象在异步操作期间被释放
        {
          if (!ec)
          {
            room_.deliver(read_msg_);
            do_read_header();
          }
          else
          {
            room_.leave(shared_from_this());
          }
        });
  }

但ascs并没有采用这种方法,原因有:

1. single_client并不要求一定要用shared_ptr包装,所以shared_from_this()不一定用得了;

2. shared_ptr引用记数的增减也是有性能开销的,ascs给了使用者一个选择可以不用引用记数,而是延时一段固定的时间即认为所有异步回调已经结束。

这个选择就是宏ASCS_DELAY_CLOSE,如果大于0,ascs将在做异步操作时不使用shared_ptr引用记数,而是在关闭连接ASCS_DELAY_CLOSE秒之后,即认为所有异步操作已经结束(已成功或者失败),对象随时可以被释放或者重用(根据有没有开启对象重用而定)。如果等于0,ascs将采用与asio例子类似的方式来管理对象的生命周期,只是不是通过shared_from_this(),原因前面说过了,而是通过引入一个新的std::shared_ptr<char> aci;到对象里面(参看tracked_executor.h),所有异步调用发起时,都将增加这个aci的引用记数(ascs手动完成),异步回调结束时减少这个aci的引用记数(shared_ptr自动完成)。

所以ascs的对象生命周期管理其实是两个shared_ptr一起决定的(ASCS_DELAY_CLOSE等于0的时候),一个是object_pool本身就是用shared_ptr管理对象的,另一个就是上面说的std::shared_ptr<char> aci;,只有两者的引用记数都归0之后,对象才会被释放或者重用,还记得前面讲连接管理的时候说过,你可以建立你的id与shared_ptr<socket>的映射关系,但当连接断开之后,必须从你的映射里面删除,否则这个socket将永远不会被释放或者重用,原因就在这里。

那么如果你想要发起一个异步回调(on_msg_handle就是在异步回调里面做的),怎么给aci增加引用记数呢?如果不给aci增加引用记数,又可能会出现上面说的段错误进程崩了的情况,答案是调用tracked_executor的post、defer和dispatch函数,不得自己拿到io_context(在你继承的socket类的构造函数里,可以得到io_context)并通过它调用相应的post、defer和dispatch函数,因为这样会绕过对aci的引用记数的增减。

注意,定义宏ASCS_DELAY_CLOSE大于0,虽然可以得到一定的性能提升,但是是有风险的,所以最好能定义得大点,以尽量保证在ASCS_DELAY_CLOSE秒之后,所有回调都结束了(已成功或者失败)。对于single_client,即便你定义宏ASCS_DELAY_CLOSE为0,仍然可能会有问题,因为single_client的生命周期是由你来控制的,aci控制得再好,如果single_client都释放了,也是白搭(前面说过了,aci是tracked_executor的一个成员变量,socket继承自tracked_executor,而single_client又继承自socket),最好能让single_client拥有和进程相同的生命周期。同理,multi_client和server的生命周期也是你控制的,它们被释放了,再怎么控制aci都白搭,它们最好也拥有与进程相同的生命周期。

为保险起见,我建议不要定义ASCS_DELAY_CLOSE宏,它的默认值是0。

 

下面再谈谈使用者显示创建的ascs对象的生命周期管理以及释放先后顺序要求,使用者显示创建的对象大概有server(包括server_base,server是它的一个typedef,其它对象也一样,不再赘述),single_client,unix_single_client,multi_client,unix_multi_client,single_socket_service(udp),unix_single_socket_service(udp),multi_socket_service(udp),unix_multi_socket_service(udp),service_pump 和 asio::ssl::context(仅用于ssl的single_client)。其中tcp 和 ssl 有相同的类(除了ssl没有unix domain socket),只是命名空间不同;另外我说的使用者显示创建的对象,也包括了你从这些对象继承,然后显示创建这些继承对象。这些对象中,可以分为两大类,一类是service_pump 和 asio::ssl::context,称之为控制型对象,另外一类是其它上面列出的对象,称之为网络对象;控制型对象的生命周期必须长于网络对象,但控制型对象之间的生命周期没有谁长谁短的要求;结束网络对象不能通过让其生命周期结束或者显示调用其析构函数(包括用delete关键字),而是应该通过调用service_pump的stop_service来实现,stop_service有两个版本,一个带一个i_service*参数用于结束指定的某个网络对象,一个不带任何参数用于结束所有网络对象,如果你调用的是第一个版本,则调用之后你仍然不能释放你结束的那个网络对象,因为它是异步的,stop_service的返回并不代表网络对象的结束(里面可能还有异步操作未结束),因为service_pump只知道还有没有异步调用,并不知道某一个网络对象上还有没有异步调用,一个service_pump可以管理许多网络对象;第二个版本则是同步的,调用之后就可以释放所有网络对象和控制型对象了。所以我的建议是(不管怎样,最终的结束都应该以调用无参数版本stop_service为结束,所以虽然网络对象的生命周期会短于控制型对象,但不得早于调用stop_service之时结束网络对象的生命周期):

一:让网络对象和控制型对象都拥有与进程相同的生命周期(比如全局变量);

二:如果你确实需要在运行时开启关闭某种服务,控制型对象仍然参考第一条,对于需要运行时开启关闭的某个网络对象,开启时通过new创建它,关闭时调用stop_service(i_service*),但关闭之后一直保留那个网络对象指针不释放,直到进程结束(当然,进程结束之前你还要调用无参数版本stop_service,调用之后就可以释放那个一直保留的网络对象指针了,如果你有强迫症的话)。

三:如果你需要在运行时开启关闭某种服务许多次,那第二条里面的方法就麻烦了,每启停一次,就会多一个网络对象指针,还得找个动态增长的容器来存放,解决办法就是,每次动态启停服务都用一个single_service_pump,与其它所有一直存在的服务所使用的service_pump分开,具体用法是(以server和ssl和single_client为例):

auto s = new single_service_pump<server>();
s->start_service();
...
s->stop_service();
delete s;


auto ssl_context = new asio::ssl::context();
...
auto c = new single_service_pump<ascs::ext::ssl::single_client>(*ssl_context);
c->start_service();
...
c->stop_service();
delete c;
delete ssl_context;

四:对于控制型对象,再引申一下,保留service_pump很容易理解,因为它是控制中枢,退出服务时需要它;但对于asio::ssl::context,保留它的原因是因为它不支持复制,只能移动拷贝,为了与st_asio_wrapper库保持一致(它不需要c++0x支持,所以没有移动拷贝),网络对象没有移动拷贝asio::ssl::context到它内部供以后使用(重用时使用),而用的是引用(asio也用的是引用),所以只能麻烦使用者来维护其生命周期;改用指针是可以解决这个问题的,但为了保持库的向前兼容性,且我个人也不喜欢指针,而且只有使用ssl single_client(_base)时才需要asio::ssl::context,所以勉强维持现状,希望大家理解。

上一篇 ascs 简明开发教程(16) 下一篇 ascs 简明开发教程(18)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值