「増補改訂版 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++による実装方法については、以下のページが参考になると思います。
デザインパターン(マルチスレッド)まとめ
開発環境 | Visual Studio 2008 Professional Edition |
ライブラリ | boost 1.34.1 |
boost::function
とboost::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); }
以下のようなメッセージを確認できます。
一撃必殺。
以下のように最後の_1
を忘れなければ問題ないようです。
boost::function<void (int)> Func = boost::bind(&A::f, &a, _1);
開発環境 | 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
Func
がboost::shared_ptr
を保持しているようです。
前回のようにデストラクタが先に実行されることはなく、前々回のようにObject
のコピーがたくさん発生することもありません。完璧。
boost::function
とboost::shared_ptr
を使うと、
継承を使わなくても多態性を実現できるようです。
GoFのパターンに応用すると実装が簡潔にできそうです。
特にCommandパターンとは相性が良いのではないかと思います。
以上。続きません。
開発環境 | 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パターンを実装するときに使うと便利そうです。
例えば、Subject
のAttach
関数を、void Attach(boost::function<void ()>
にしておくと、
Observer
側は面倒な階層構造を持つ必要がなくなります。
次回に続きます。
開発環境 | 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
のコピーを保持しているのでしょう。
次回に続きます。
開発環境 | C/C++,WIN32 |
本記事は過去に書いた記事の焼き直しです。
Windowsアプリケーションの中には、ウィンドウのサイズ変更に時間がかかるものがあります。
代表的なものは画像ビューアです。画像の拡大縮小を綺麗にやろうとすると時間がかかるからです。
この時間がかかる処理をOnSize
(WM_SIZE
のハンドラ)に書くと、ウィンドウのサイズ変更がもっさりとしたアプリケーションになってしまいます。
本日紹介する方法は、サイズ変更のもっさり感を解消するための方法です。
まずは軽くて適当なサイズ変更処理を用意します。
例えば画像ビューアではニアレストネイバー法による拡大縮小処理が良いでしょう。
Windowsでは、サイズ変更前にWM_ENTERSIZEMOVE
が送られてきて、サイズ変更後にWM_EXITSIZEMOVE
が送られてくるので、
このメッセージの間にWM_SIZE
が送られてきた場合は、軽くて適当なサイズ変更処理を実施します。
そして、WM_EXITSIZEMOVE
で重くて正確なサイズ変更処理を実施するのです。
これでユーザがウィンドウフレームをドラッグしているときは軽いサイズ変更処理が行われるので、もっさり感はなくなります。
これらのメッセージを使うときの注意点を挙げます。
WM_ENTERSIZEMOVE
とWM_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; }