4

我正在研究一组使用 boost::asio 来执行后台任务的类。在实践中,程序会连续运行,但我在测试期间添加了信号处理程序进行清理。

但是,当在收到 SIGINT 后监视代码中的函数调用时,我发现我的对象的私有实现没有按预期被破坏 - 内存泄漏。它由 boost::shared_ptr 管理。私有实现类如下所示。

class TestImpl: public boost::enable_shared_from_this<TestImpl>, boost::noncopyable {
    TestImpl(): update_timer(io_svc), signals(io_svc, SIGINT, SIGTERM) {
        signals.async_wait(boost::bind(&boost::asio::io_service::stop, &io_svc)); 
    };

public:
    virtual ~TestImpl() { 
        std::cout << "Destroyed." << std::endl;
    };

    static boost::shared_ptr<TestImpl> create() {
        boost::shared_ptr<TestImpl> ptr(new TestImpl);
        ptr->start();
        return ptr;
    }

    void start() {
        update_timer.expires_from_now(boost::posix_time::seconds(1));
        update_timer.async_wait(boost::bind(&TestImpl::update, shared_from_this()));    

        run_thread = boost::thread(boost::bind(&TestImpl::run, shared_from_this()));
    };

    void cleanup() {
        run_thread.join();
    };

private:
    void run() {
        io_svc.run();
    };

    void update() {
        std::cout << "Updating." << std::endl;
        update_timer.expires_from_now(boost::posix_time::seconds(1));
        update_timer.async_wait(boost::bind(&TestImpl::update, shared_from_this()));    
    };

    boost::asio::io_service io_svc;
    boost::asio::deadline_timer update_timer;
    boost::thread run_thread;
    boost::asio::signal_set signals;
};

这是使用私有实现的代码。

class Test {
public:
    Test(): impl(TestImpl::create()) { };
    virtual ~Test() { std::cout << "Destroyed." << std::endl; };
    int run() {
        boost::asio::signal_set signals(io_svc, SIGINT, SIGTERM);
        signals.async_wait(boost::bind(&boost::asio::io_service::stop, &io_svc));

        io_svc.run();

        impl->cleanup();

        return 0;
    };
private:
    boost::asio::io_service io_svc;
    boost::shared_ptr<TestImpl> impl;
};

int main() {
    Test test;
    test.run();
}

我无法理解为什么 TestImpl 类被泄露。通过调试,我可以验证两个 io_service 实例都在 SIGINT 时停止并且线程已加入,这使我相信它不会在销毁时分离。似乎某处必须有一个循环引用导致 TestImpl 实例持续存在?

4

1 回答 1

4

循环引用在TestImpl和之间TestImpl::io_svc

  • TestImpl::io_svc的生命周期取决于TestImpl它是一个成员变量。
  • TestImpl的生命周期间接取决于TestIMpl::io_svc由于shared_from_this()被绑定为在io_service.

一个关键细节是io_service::stop()只影响事件处理循环;它不会影响处理程序的生命周期或绑定到处理程序的参数。从 中删除处理程序的唯一方法io_service是通过io_service析构函数。以下是文档的相关摘录:

io_service计划在或任何关联链上延迟调用的未调用处理程序对象将被销毁。

[...]

要关闭整个程序,调用该io_service函数以尽快stop()终止任何调用。run()上面定义的io_service析构函数会销毁所有处理程序,导致shared_ptr对所有连接对象的所有引用都被销毁。

要解决此问题,请考虑将 Boost.Asio I/O 对象的生命周期与TestImpl. 我个人会选择使用boost::optionaloverboost::shared_ptr来最小化内存分配量。

TestImpl()
  : io_svc(boost::in_place()),
    update_timer(boost::in_place(boost::ref(io_svc.get()))),
    signals(boost::in_place(boost::ref(io_svc.get()), SIGINT, SIGTERM))
{
  signals->async_wait(boost::bind(&boost::asio::io_service::stop,
                                  boost::ref(io_svc))); 
};

...

void cleanup() {
  run_thread.join();
  signals      = boost::none;
  update_timer = boost::none;
  io_svc       = boost::none;
};

...

boost::optional<boost::asio::io_service> io_svc;
boost::optional<boost::asio::deadline_timer> update_timer;
boost::optional<boost::asio::signal_set> signals;
boost::thread run_thread;
于 2013-03-28T03:41:20.187 回答