ぬうぱんの備忘録

技術系のメモとかいろいろ

作った曲一覧はこちら

boost.asioの非同期IOのしくみ

なにがあった

 一個前のエントリで同期通信のブロッキングがウンタラカンタラって言って結局非同期通信することに落ち着いたので、非同期通信がどうなってるのかをメモ

基本的な流れとか

 おおよそ次の3ステップ

  1. メインスレッドでio_serviceに送受信バッファとハンドラを登録(post)
  2. io_serviceは登録されたハンドラを処理する(dispatch)
  3. 送受信が完了すると登録したハンドラが呼び出される

補足すると

  • メインスレッドから仕事を投げて子スレッドで仕事を処理するイメージ。
  • 2はio_service::run()内のメッセージループで行われる。
  • 送信にしても受信にしてもハンドラが呼び出されるまでは送受信が終了していないという事なので、バッファを有効にしておく必要がある。

メッセージループの開始と終了

 開始と終了が割と面倒。というのも

  • ハンドラを処理するメッセージループはio_service::run()で開始する
  • io_service::run()は処理の間ブロッキングするので別スレッドで呼び出す必要がある
  • io_service::run()は登録されたハンドラをすべて処理した地点で終了する

 つまり、予めio_service::run()を呼び出しておいて、後から逐次ハンドラを登録するという手は、ただでは使えないということ。
 しかし、ちゃんと方法は用意されていて

  • io_service::workを関連付けて登録しておくと次の2つの条件を満たすまでio_service::run()は終了しなくなる。
    • io_service::workが死ぬ
    • 溜まっているハンドラがすべて処理される

 2つめの条件がなかなか曲者。未処理のハンドラが溜まっている場合、関連付けたio_service::workを殺すだけではio_service::run()は終了しない。そのため、io_service::stop()でメッセージループの停止を要求する必要がある。

そもそもの使い方としては

 送受信のハンドラをまとめて登録してすべて登録したらio_service::run()を呼び出す、といった感じなのだろう。で、次の登録をするときはio_service::reset()を呼び出しておくみたいな。

まとめると

流れとしてはこんなかんじ

void NetworkIO(){
    io_service IoService;
    io_service::work *pWork = new io_service::work(IoService);

    //is_service::run()を別スレッドで走らせる
    //例えば
    boost::thread RunThread(&io_service::run, &IoService);

    //IoServiceからソケット作ってハンドラの登録とか

    delete pWork;
    IoService.stop();

    //IoServiceRunThread()の終了を待つ
    RunThread.join();
}

そのほか注意点

  • io_service::run()を一度抜けてまた呼び出すときはio_service::reset()で初期化しておかないとマズイ。

追記

2013/02/09
io_service::run()の走らせ方が頭悪かったので修正。
bindしてしまえば、自分で定義してない関数でもスレッド化できますよねそりゃ。