forkとthread

PARDSでは、SPAWN(foo()); などとすることで、foo()という関数を別プロセスとしてforkします。threadにした方がオーバヘッドは少ないのですが、メモリ空間を共有することによるバグを避けるため、あえてforkを使うようにしています。この性質は、特に既存のプログラムを並列化する際に役に立つと思います。

しかし、オーバヘッドが大きいことが問題になる場合もあります。これは、並列に計算する単位(粒度)を小さくしたい場合です。(粒度が十分に大きければ、このオーバヘッドは問題になりません。逆に言うと、粒度を小さくしないと十分な並列度が稼げない場合に問題があります。)というわけで、threadでも簡単に書けるようにできないか、考え中。

forkの場合は、SPAWNマクロは、例えば

#define SPAWN(func) {if(fork()==0) {func; _exit()}} 

のように書けますが(実際はもう少し複雑です)、pthreadの場合はこう単純には書けません。pthread_createの引数に取れる関数の型が決まっているためです(void*を引数に取り、void*を返す)。で、どうしようかと。

cppマクロの技芸を尽くせば何とかなりそうなので、少し考えてみた:
ライブラリ側で、

class THR_ROOT{
 pubic: 
  virtual void run() = 0;
}

#define DEFINE_THR1(rt,fn,t1,a1) \
class THR_##fn : public THR_ROOT {\
 public: \
  t1 a1; \
  THR_##fn(t1 x1){a1 = x1;} \
  rt fn(); \
  void run(){fn();} \
}; \
rt THR_##fn::fn() // 関数定義が続く。仮引数への参照はメンバ変数への参照に

#define SPAWN_THR1(fn,a1) \
{ \
 pthread_t thr; \
 THR_##fn *tmp = new THR_##fn(a1); \
 pthread_create(&thr,NULL,launcher,(void*)tmp); \
}

void* launcher(void* arg)
{
 ((THR_ROOT*)arg)->run();
}

としておきます。ライブラリユーザは、関数定義を

// void foo(int a)
DEFINE_THR1(void,foo,int,a)
{
 ...a
}

とすれば、呼び出し側で

int main()
{
 ...
 SPAWN_THR1(foo,1);
 ...
}

と呼び出せます。(手で打ったので、間違いがあるかも。)

このトリックでは、DEFINE_THR1で、関数定義を「THR_ROOTクラスから継承した、関数の仮引数をメンバ変数に持つclass定義」にすりかえます。もともとの関数はそのクラスのメンバ関数になります。THR_ROOTクラスで定義されたrun()メンバ関数はoverrideし、その中からくだんのメンバ関数が呼び出されるようにします。
呼び出し側のSPAWN_THR1では、先ほどのclassのインスタンスを作って、それを引数にlauncher関数を呼びます。launcher関数ではTHR_ROOTクラスのrun()を呼び出します。そうすると、先ほど定義されたメンバ関数が呼び出される、という寸法です。

というか、ややこしすぎ。

この方法の欠点は、

  • スレッド定義や呼び出しで、関数名、仮引数の型名と仮引数の間にカンマ","が必要で気持ち悪い
  • 引数の数ごとに別のマクロを使う必要がある
  • スレッド定義がスレッド呼び出しの前にある必要がある。スレッド定義を別ファイルにしたい場合は、class宣言だけを行うマクロと、メンバ関数の定義だけを行うマクロに分けないと…
  • …等々

なかなかforkの時のようにはきれいに行きませんな。もう少し考えてみるか。