2008年03月24日

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編」 という本を読みましたので感想などを書いてみます。

本書は、マルチスレッドソフトウェアで頻繁に使用される12種類のソフトウェアパターンの紹介と、 JavaのサンプルコードやTipsを集めた本です。

本書には、synchronized、wait、notifyの使い方など、タイトル通りJava特有の部分もありますが、 パターンのコンセプトや利点・欠点などは言語を問わない共通の部分もありました。 私はC++ユーザなので、後者を特に興味深く読ませて頂きました。

本書で紹介されているパターンは、オブジェクト指向における再利用のためのデザインパターン にあるような応用的なものというよりも、 マルチスレッドソフトウェアを作るために必須となる基本的なものでした。

例えば、本書記載のSingle Threaded Executionパターンは、クリティカルセクションのことなので、 これを使わずにマルチスレッドソフトウェアを作ることは無理でしょう。 Thread-Per-Message又はWorker Threadパターンも少なくとも片方は必須になるでしょうし、 安全に終了するマルチスレッドソフトウェアを書こうとしたらTwo-Phase Terminationパターンは必須になります。
その他のパターンも基本的なものが多く、まともに動作するマルチスレッドソフトウェアを作ったことがある人は、 ほとんどのパターンを使ったことがあると思います。
最後に紹介されているActive Objectパターンはちょっと大掛かりなので使ったことがない人も居るでしょうが、 分散プログラミングをやったことがある人にはお馴染みの構成でしょう。

よって、今までマルチスレッドソフトウェアを作ったことがないという人にはお勧めの一冊です。 言語を問わず使えると思います。

マルチスレッドに関わったことがある人にとっては、新しいノウハウを提供できるという本ではないと思いますが、 それでも、共通の語彙を得られるという点でお勧めできます。
特に、マルチスレッドソフトウェアについての解説記事を書く方にお勧めです。
例えば、「メインのスレッドがこの処理を実行するためのオブジェクト作り、それを裏で動いているスレッドに渡して実行する方式です」 という長い分かり難い説明が、「Worker Threadパターンです」という説明で終わります。

他にも、クラス名や関数名を本書の内容と合わせると、分かりやすいコードになると思いますので、 命名に悩んでいるマルチスレッドソフトウェア開発者にもお勧めです。

なお、本書記載のパターンのC++による実装方法については、以下のページが参考になると思います。
デザインパターン(マルチスレッド)まとめ

2008年03月15日

boost::function + bindの破壊力

開発環境Visual Studio 2008 Professional Edition
ライブラリboost 1.34.1

boost::functionboost::bindの組み合わせは結構強力なようです。 以下のコードをコンパイルしてみると、

#include <boost/bind.hpp>
#include <boost/function.hpp>

class A
{
public:
  void f(int) {}
};

void main()
{
  A a;
  boost::function<void (int)> Func = boost::bind(&A::f, &a);
}

以下のようなメッセージを確認できます。
VS 2008
一撃必殺。

以下のように最後の_1を忘れなければ問題ないようです。

boost::function<void (int)> Func = boost::bind(&A::f, &a, _1);

2008年03月11日

boost::function その3

開発環境Visual C++ 2008

前回の続きです。
前回FuncオブジェクトにObjectへのポインタを渡していましたが、今回はboost::shared_ptr<Object>を渡してみました。

void Test3B(void)
{
  boost::function<void ()> Func;
  {
    boost::shared_ptr<TestClass> Object(new TestClass);
    Func = boost::bind(&TestClass::Test, Object);
  }
  Func();
}

コンパイルは問題なし。boost::shared_ptrも使えるんですね。
実行結果は以下の通り。

Constructed this=00387600
Func this=00387600
Destructed this=00387600

Funcboost::shared_ptrを保持しているようです。 前回のようにデストラクタが先に実行されることはなく、前々回のようにObjectのコピーがたくさん発生することもありません。完璧。

boost::functionboost::shared_ptrを使うと、 継承を使わなくても多態性を実現できるようです。
GoFのパターンに応用すると実装が簡潔にできそうです。 特にCommandパターンとは相性が良いのではないかと思います。

以上。続きません。

2008年03月10日

boost::function その2

開発環境Visual C++ 2008

前回の続きです。
前回FuncオブジェクトにObjectそのものを渡していましたが、今回はポインタを渡してみました。

void Test2B(void)
{
  boost::function<void ()> Func;
  {
    TestClass Object;
    Func = boost::bind(&TestClass::Test, &Object);
  }
  Func();
}

Objectのスコープ外でFunc()を実行してみました。
実行結果は以下の通り。

Constructed this=0012FE5B
Destructed this=0012FE5B
Func this=0012FE5B

デストラクタの後にメンバ関数Testが呼び出されているようです。これは問題ありそうです。 やっぱりスコープ外で実行してはいけなかったようです。 しかし、スコープ内で実行する場合であれば、前回のように大量のコピーが発生しない分、今回の方が良いでしょう。

GoFのObserverパターンを実装するときに使うと便利そうです。
例えば、SubjectAttach関数を、void Attach(boost::function<void ()>にしておくと、 Observer側は面倒な階層構造を持つ必要がなくなります。

次回に続きます。

2008年03月09日

boost::function その1

開発環境Visual C++ 2008

boostにはFunctionというライブラリがあります。 一言でいうと強化型関数ポインタですが、結構多機能です。 特にメンバ関数周りの機能が充実していて、以下のようにメンバ関数を格納した上に呼び出すこともできます。

boost::function<void ()> Func;
TestClass Object;
Func = boost::bind(&TestClass::Test, Object);
Func();

最後の行では、Objectを使わないでメンバ関数を呼び出しています。 こんなことをして大丈夫なのか気になりましたので調べてみました。

class TestClass
{
public:
  TestClass(void) {printf("Constructed this=%p\n", this);}
  TestClass(const TestClass& Test) {printf("Copy this=%p from=%p\n", this, &Test);}
  ~TestClass(void) {printf("Destructed this=%p\n", this);}
  void Test(void) {printf("Func this=%p\n", this);}
};

void Test1(void)
{
  boost::function<void ()> Func;
  {
    TestClass Object;
    Func = boost::bind(&TestClass::Test, Object);
  }
  Func();
}

Objectのスコープ外でFunc()を実行してみました。
実行結果は以下の通り。

Constructed this=0012FE5B
Copy this=0012FD54 from=0012FE5B
Copy this=0012FC28 from=0012FD54
Copy this=0012FB20 from=0012FC28
Copy this=0012FC4F from=0012FB20
Destructed this=0012FB20
Destructed this=0012FC28
Copy this=0012FD5C from=0012FC4F
Destructed this=0012FC4F
Destructed this=0012FD54
Copy this=0012FC10 from=0012FD5C
Copy this=0012FB00 from=0012FC10
Copy this=0012F9F8 from=0012FB00
Copy this=0012F8E0 from=0012F9F8
Copy this=0012F7D8 from=0012F8E0
Copy this=0012F6C0 from=0012F7D8
Destructed this=0012F6C0
Destructed this=0012F7D8
Destructed this=0012F8E0
Copy this=0012F8DC from=0012F9F8
Copy this=0012F7B0 from=0012F8DC
Copy this=0012F67C from=0012F7B0
Copy this=0012FC48 from=0012F67C
Destructed this=0012F67C
Destructed this=0012F7B0
Destructed this=0012F8DC
Destructed this=0012F9F8
Destructed this=0012FB00
Destructed this=0012FC10
Copy this=0012FBE0 from=0012FC48
Destructed this=0012FC48
Copy this=0012FE70 from=0012FBE0
Destructed this=0012FBE0
Destructed this=0012FD5C
Destructed this=0012FE5B
Func this=0012FE70
Destructed this=0012FE70

たくさんコピーが行われているようです。ちょっとびっくり。 Func()実行時の出力はFunc this=0012FE70です。 デストラクタは一番最後に呼び出されているようですので、Func()実行時はオブジェクトは存在するようです。
おそらく、FuncオブジェクトがObjectのコピーを保持しているのでしょう。

次回に続きます。

2008年03月02日

WM_ENTERSIZEMOVEとWM_EXITSIZEMOVE

開発環境C/C++,WIN32

本記事は過去に書いた記事の焼き直しです。

Windowsアプリケーションの中には、ウィンドウのサイズ変更に時間がかかるものがあります。
代表的なものは画像ビューアです。画像の拡大縮小を綺麗にやろうとすると時間がかかるからです。
この時間がかかる処理をOnSize(WM_SIZEのハンドラ)に書くと、ウィンドウのサイズ変更がもっさりとしたアプリケーションになってしまいます。
本日紹介する方法は、サイズ変更のもっさり感を解消するための方法です。

まずは軽くて適当なサイズ変更処理を用意します。 例えば画像ビューアではニアレストネイバー法による拡大縮小処理が良いでしょう。
Windowsでは、サイズ変更前にWM_ENTERSIZEMOVEが送られてきて、サイズ変更後にWM_EXITSIZEMOVEが送られてくるので、 このメッセージの間にWM_SIZEが送られてきた場合は、軽くて適当なサイズ変更処理を実施します。 そして、WM_EXITSIZEMOVEで重くて正確なサイズ変更処理を実施するのです。
これでユーザがウィンドウフレームをドラッグしているときは軽いサイズ変更処理が行われるので、もっさり感はなくなります。

これらのメッセージを使うときの注意点を挙げます。

  • WM_ENTERSIZEMOVEWM_EXITSIZEMOVEは、ウィンドウを移動するときにも送られてきます。 よって、WM_EXITSIZEMOVEを受け取ったときはサイズ変更が実際に発生したかどうか判定するべきです。
  • システムメニューからサイズを変更した場合など、WM_ENTERSIZEMOVE無しにWM_SIZEが送られてくるときもあります。 このときは、正確なサイズ変更処理を行うべきです。

コードを以下に示します。上記注意点もケアされています。
ウィンドウメッセージの処理は、ご使用のライブラリの作法に合わせて変えて下さい。

// グローバル変数orウィンドウクラスのメンバ変数
bool SizeChanging_;
bool SizeChanged_;

// WM_SIZEのハンドラ
LRESULT OnSize(UINT, WPARAM, LPARAM, BOOL&)
{
  if (!SizeChanging_) {
    // 重たくて正確なサイズ変更処理
  }else {
    SizeChanged_ = true;
    // 軽くて適当なサイズ変更処理
  }
  return 0;
}

// WM_ENTERSIZEMOVEのハンドラ
LRESULT OnEnterSizeMove(UINT, WPARAM, LPARAM, BOOL&)
{
  SizeChanging_ = true;
  SizeChanged_ = false;
  return 0;
}

// WM_EXITSIZEMOVEのハンドラ
LRESULT OnExitSizeMove(UINT, WPARAM, LPARAM, BOOL&)
{
  SizeChanging_ = false;
  if (SizeChanged_) {
    // 重たくて正確なサイズ変更処理
  }
  return 0;
}