はじめに
前編では、16bitOSだった時代から伝わる伝統的な手法(メッセージポンプ)を紹介しました。反応が鈍いと感じはするものの、同時に複数の処理を実行する手段を持たなかった時代だったため、不自由しているという感覚はありませんでした。しかし、そのような時代は既に歴史の1ページに過ぎず、32bit(あるいは64bit)OSの現在では、こういった処理はそれぞれ別に実行し、同時に処理するマルチスレッドを利用するのがよいとされています。
後編となる今回は、マルチスレッドを利用し、時間のかかる処理とメッセージの処理の2つを効率よく行う方法のいくつかを紹介したいと思います。
対象読者
- C++でWindowsアプリケーションのプログラム開発経験がある開発者
必要な環境
- Visual C++ 2010 Express Editionまたはそれ以上のエディションがインストールされた環境
どの処理をメインスレッドに残すか
マルチスレッド化するとき、最初に考える問題がどの処理を別のスレッドに移動するかというものです。前編でも触れたようにWindowsは、メッセージ処理を適切に行うことをプログラムに要求しています。そして、ほぼ例外なくGUIアプリはメインスレッドにメッセージループを持ちます。そのため、メインスレッドは必ずメッセージを処理する必要があります。結果論ではありますが、メッセージ処理を必要としない処理を別スレッドに持っていくということが半ば必然となります。
最初は究極的なマルチスレッド化です。このサンプルのように時間がかかる作業であってもその終了を待つ必要がなければ有効です。
#include <process.h> unsigned __stdcall BusyLoopThProcEx( void* param ) { DWORD dwTime = *static_cast( param ); DWORD dwStart = GetTickCount(); while( (GetTickCount()-dwStart) < dwTime ); return 0; } void ThreadBusyLoopNoWait( DWORD dwTime ) { unsigned thrdaddr; HANDLE hThread = reinterpret_cast<HANDLE>( _beginthreadex( NULL, 0, BusyLoopThProcEx, &dwTime, 0, &thrdaddr ) ); if( hThread != NULL ){ CloseHandle( hThread ); } }
タスクマネージャなどの、CPU利用率を見れる状態で動作させてみてください。別途時間のかかる処理をしているらしいピーク状態を見せながら、なんの不都合もなく、プログラムが動いているのが分かると思います。今度は、時間のかかる処理を行っている最中にウィンドウを閉じてみてください。
前編ではプログラムが終了せず、事実上のゾンビ状態になっていましたが、今度は時間のかかる処理の途中でプログラムが終了してしまいます。ゾンビにならないからいいのでは、という見方もありますが、途中で処理を放棄(実際には強制的にシャットダウンされる)となるため、ファイルへの書き込みなどがある場合は致命的な不具合につながる可能性があります。
時間のかかる処理であっても、情報を渡したら後は勝手に自己完結的に処理を行うものであれば、このように待たずに次の作業ができるようにするのは有効な手段となります。しかし、最初の設計から別に動作するという前提を盛り込んでいない限りこのような実装にすることはできません。従って、前編同様何らかの形で処理終了を待たなければなりません。
Windowsのスレッドには、メッセージを処理するスレッド(一般にUIスレッドなどと呼ばれる)と、メッセージを処理しないスレッド(一般にワーカースレッドなどと呼ばれる)の2種類があります。前者はUIスレッドと呼ばれるようにウィンドウを持ち、そのウィンドウが動くためにメッセージループを持つという特性を持ちます。それに対し、ワーカースレッドはユーザーインターフェースを一切もたないことで、メッセージループを回さなくてよいという特性を持ちます。