ぷろあくた\(^o^)/ ぱ〜とつぅ〜

Boost.Asio はもうちょっと↓みたいに proactor な部分で遊ぶのを前面に押し出してほしい気がします的なっていうかこれさっきゆった.
要 Boost 1.36.0 以上.少なくとも Windows Vista 64bit (ただし WIN32 の configuration でビルド) + MSVC 8.0 では動いたような気がする.改行をコンソールに入力するとプログラムが終了するような気もしますっていうかこれもさっきゆった.

#define WIN32_LEAN_AND_MEAN
#include <cstdio>
#include <algorithm>
#include <iostream>

#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread/thread.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/deadline_timer.hpp>



class Handler
{
public:
  explicit Handler(boost::asio::io_service &proactor)
    : proactor_(proactor),
      i_(1)
  {}

  void initiate()
  {
    proactor_.post(boost::bind(&Handler::initiate, this));
    proactor_.post(boost::bind(&Handler::handle, this, i_, i_, 0, i_));
    ++i_;
  }

  void handle(int i, int src, int count, int max)
  {
    if (i == 1) {
      std::cout << "from: " << src << ", count: " << count << ", max: " << max << std::endl;
      return;
    }

    int new_i = i;
    new_i = new_i % 2 == 0 ? new_i / 2 : new_i * 3 + 1;
    std::cout << i << " -> " << new_i << " (started from " << src << ")" << std::endl;
    proactor_.post(boost::bind(&Handler::handle, this, new_i, src, count + 1, new_i > max ? new_i : max));
  }

private:
  boost::asio::io_service &proactor_;
  int i_;
}; // class Handler



void threadMain(boost::asio::io_service &proactor)
{
  Handler h(proactor);
  proactor.post(boost::bind(&Handler::initiate, boost::ref(h)));
  proactor.run();
}



int main()
{
  boost::asio::io_service proactor;
  boost::thread th(boost::bind(&threadMain, boost::ref(proactor)));
  std::fgetc(stdin);
  proactor.stop();
  th.join();
}

非同期イベント (非同期 I/O の完了, accept の request, タイマの時間切れ, etc.) が通知されたら,通知を queue に溜め込む. queue にたまっている通知を取りにきたスレッドがあれば,そのスレッドに通知に対応させた handler を実行させる.以上が proactor (boost::asio::io_service) の本来の仕事.そうではなくて,上のコードでは非同期処理 (非同期 I/O, タイマの非同期待ち, etc.) を伴うことなく,直接 proactor に (適当な handler に関連付けた) 通知を送り込んで (proactor_.post), handler, つまり関数の呼び出しを行っているように見せかけている.

具体的に handler を駆動しているスレッドの流れを追ってみましょう.

void threadMain(boost::asio::io_service &proactor)
{
  Handler h(proactor);
  proactor.post(boost::bind(&Handler::initiate, boost::ref(h)));

(この関数を別スレッドで動かしているのは,コンソール入力を受け付けるスレッドが別に欲しかったからで,それ以上の意味はありません.) proactor に boost::bind(&Handler::initiate, boost::ref(h)) に関連付けた通知を送り込む. Handler::initiate は直ちに実行されるのではなく, proactor 内部にスレッドの実行が移った際に初めて実行される.

  proactor.run();
}

スレッドの実行を proactor 内部に移す.これ以降, (アプリケーションスレッドが proactor.stop() を実行するまで) このスレッドの実行は proactor_.run() から出てくることはない.

スレッドの実行が proactor 内部に移ると,通知がある場合には通知に関連付けられた handler を実行し,通知がない場合は新たに通知が来るまでスレッドは待たされる. proactor_.run() を実行した瞬間は,先に Handler::initiate に関連付けられた通知を送ってあるため,すぐに Handler::initiate の実行に移る.

  void Handler::initiate()
  {
    proactor_.post(boost::bind(&Handler::initiate, this));
    proactor_.post(boost::bind(&Handler::handle, this, i_, i_, 0, i_));
    ++i_;
  }

Handler::initiate では Handler::initiate, つまり自分自身を呼び返すよう proactor に要求する.

Handler::initiate では,同時に i_ == 1 として Handler::handle の呼び返しも要求する.

最後に i_ を increment して Handler::initiate の実行は終了する.

Handler::initiate の実行が終了すると,スレッドの実行は proactor 内部に戻る.先の Handler::initiate は proactor 内部から呼び出されたからだ. proactor 内部に実行が戻ると, Handler::initiate に関連付けられた通知があるために (ついさっき Handler::initiate で要求された呼び返しである) 再び Handler::initiate に実行を移す. Handler::initiate 内部では再び自分自身の呼び返しと, i_ == 2 での Handler::handle の呼び返しを要求し, i_ を increment して proactor 内部に実行を戻す.

Handler::initiate は呼び出されるたびに自分自身を呼び返すよう proactor に要求し続けるため,以降, Handler::initiate は連鎖的に呼び出され続ける.

proactor 内部に実行が戻ったあと,次に実行されるのは Handler::handle(1, 1, 0, 1) である.最初に Handler::initiate を呼び出した際に要求した Handler::handle の呼び返しを処理するためである. (ただし, OS や proactor の事情によっては,後の要求が先に処理される可能性も否定はできない)

Handler::handle は,Handler::initiate がそうであるように,自分自身の呼び返しを要求するため連鎖的に呼び出され続ける.ただし,最初の引数が1の場合だけ自分自身の呼び返しを要求しないまま実行を proactor 内部に戻すので,このときに Handler::handle の呼び返しの連鎖は停止する.

以下の実行の流れは同様.

proactor_.post(f) はおよそ関数 f の呼び出しとみなせる.ただし通常の関数呼び出しとは,関数の呼び出しと関数の実行が時間的に分離する点で異なる.

通常の関数呼び出しは,関数呼び出しの直後にその関数の実行が起きる,最後に呼び出した関数が最初に実行される, LIFO, スタック.

proactor_.post(f) を関数呼び出しとみなした場合,関数の実行が queue に入れられて pending されるものとみなせる.最初に呼び出していた関数が最初に実行される, FIFO, きぅ.

以上のように,通常の関数呼び出しとは異なるものの, proactor に関数の呼び返しを要求することで直接の関数呼び出しに代えることもできる,呼び返しを連鎖させることで再帰に代えることもできる,というどーでも良い話ですた.

っていうか別にこんな「120の系列の処理が始まってるのにまだ27から始まる系列が終わってねーwww」みたいなことをしたかったわけでも…….