如何正确关闭使用asio进行事件排队的类的实例

时间:2012-03-05 21:51:47

标签: c++ multithreading boost boost-asio shared-ptr

在我的项目中,我广泛使用boost asio来使用io_service.post()和strand.post()/ dispatch()将非均匀事件统一排队到我的应用程序中的模块。

在main()中,这些模块在shared_ptrs中创建并保存,直到程序退出并被删除:

simplified main:
{
  [some initialization]

  boost::asio::io_service service;

  [create pool of worker threads that drive the io_service]

  {
    boost::shared_ptr<manager_a> a(new manager_a(service, foo));
    boost::shared_ptr<manager_b> b(new manager_b(service, bar, blah));
    ...

    [wait for signal to shutdown (SIGINT (release), cin.getc() (debug), or similar)]
  }

  [some shutdown (join thread pool)]
}

“管理员”(我不知道该怎么称呼它们)来自boost :: enable_shared_from_this。 在他们的构造函数中,他们可以在其他模块中注册回调:

manager_a::manager_a(boost::asio::io_service& service, module *m) :
  m_service(service),
  m_strand(service),
  m_module(m)
{
  // manager_a implements some callback interface
  // that "module" may call from another thread
  m_module->register(this);
}

在析构函数中:

manager_a::~manager_a()
{
  m_module->unregister(this);
}

管理员通过链接发布呼叫来实现回调接口:

void manager_a::on_module_cb(module_message m)
{
  // unsafe to do work here, because the callback is from an alien thread
  m_strand.dispatch(boost::bind(&manager_a::handle_module_message, shared_from_this(), m));
}
void manager_a::handle_module_message(module_message m)
{
  // safe to do work here, as we're serialized by the strand
}

现在我陷入困境:

如果“module”在构造函数中的register(this)之后立即调用回调,则main中的shared_ptr尚未接管实例,并且回调函数中的shared_from_this()会引发异常。析构函数的相同问题 - shared_ptr确定它有最后一个引用,并且在调用析构函数时,如果在unregister(this)之前调用回调,shared_from_this()抛出。

需要shared_from_this()的原因是因为排队函数调用存储在io_service中,该调用包含指向管理器实例的指针,并且与管理器的生命周期无关。我可以将原始this提供给“模块”,因为我可以在销毁manager_a之前取消注册,但是对于io_service中的排队函数调用,情况也是如此,因此需要shared_from_this()只要发布了一些消息,就保持实例处于活动状态。您也无法取消这些,并且您无法阻止等待析构函数,直到所有待处理的帖子都已发送。不幸的是,就像现在一样,我甚至无法防止新帖子在关键的构造函数/析构函数阶段被排队(试图)排队。

一些想法:

  1. 写入开始/停止功能并在那里注册/取消注册。例如。创建这样的工厂函数:

    boost::shared_ptr<manager_a> manager_a::create(io_service& s, module *m)
    {
      boost::shared_ptr<manager_a> p(new manager_a(s, m));
      p->start();
      return p;
    }
    

    这仅适用于创作,而不适用于销毁。给shared_ptr一个自定义删除器,在删除之前调用stop(),没有用,因为无论如何都太晚了。

  2. 让main()调用start()和stop(),但是我没有看到main()如何以异常安全的方式执行stop()调用,即确保即使稍后会抛出一些代码,也会调用stop()。有另一个RAII类只是为了调用stop()似乎很尴尬。

  3. 只需将strand.dispatch()调用包装在try / catch中,然后忽略该异常。这表示正在进行破坏(或构建未完成),因此请忽略回调。这感觉很乱,我宁愿不这样做。如果我可以访问enable_shared_from_this基类中的嵌入式weak_ptr,我可以调用非投掷lock()并检查返回值以查看它是否有效。但是shared_from_this并没有给你访问权限,而且它仍然看起来像hackish ...此外,在构建期间错过回调也没有用,即使我可以解决这个问题。

  4. 让manager_a拥有自己的io_service和驱动它的线程。然后,在析构函数中,我可以停止服务并加入线程,并确保io_service中没有待处理的帖子。也不再需要shared_from_this(),也不需要一条链。我想这会有用,但是然后,在main()中为所有管理器创建一个线程池变得毫无意义,而且我的应用程序中有更多的线程比看起来更合理。其他模块中已有足够的线程不使用asio io_service ...

1 个答案:

答案 0 :(得分:1)

从你的问题中不清楚,但是如果你的所有经理对象都是在启动时创建的并且在退出时被删除,那么问题的解决方案是在启动IO线程之前创建管理器,并停止IO线程在删除管理器之前。当你不想要它们时,这将阻止你获得回调。