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_svcTestImplの寿命は、メンバー変数であるため依存します。
  • 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::optionaloverを使用することを選択します。boost::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 に答える