マルチスレッドとは、スレッドを複数同時に動かすことです。普通のアプリケーションは
基本的に1つのスレッドで動いています。
マルチスレッドと聞くと難しそうですが、MFCでは意外と簡単に出来ます。
ちなみに、「Windowsが擬似マルチスレッドだ〜」というような議論は、ここではしません。
よく分からないしぃ。
典型的なマルチスレッドの使い方としては、
画面表示やユーザーからの入力を行うインターフェーススレッドと、
計算をごりごり行うワーカースレッドの2つのスレッドに分けるものがあります。
例えば、ワーカースレッドで計算を行い、その計算の進み具合を
インターフェーススレッドのプログレスバーで表示するというものです。
ここでは、「バージョン情報」のダイアログにプログレスバーを付け、 ワーカースレッドで値を1ずつ増加させる処理を行い、 プログレスバーにその状況を表示させるサンプルを示します。
まずは、ワーカースレッドを使わない形式のプログレスバーのプログラムを示します。 この構造については、プログレスバーの説明を参照して下さい。
test.cpp:class CAboutDlg : public CDialog { public: CAboutDlg(); // ダイアログ データ //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; CProgressCtrl m_Progress1; //}}AFX_DATA // ClassWizard は仮想関数を生成しオーバーライドします。 //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV のサポート virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); //}}AFX_VIRTUAL // Implementation protected: //{{AFX_MSG(CAboutDlg) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: int m_nValue; BOOL m_bDraw; }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT m_bDraw=FALSE; } 〜 const int MAX=1000; BOOL CAboutDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: この位置に初期化の補足処理を追加してください m_Progress1.SetRange(0,MAX); m_nValue=0; return TRUE; // コントロールにフォーカスを設定しないとき、戻り値は TRUE となります // 例外: OCX プロパティ ページの戻り値は FALSE となります } LRESULT CAboutDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください if(message==WM_PAINT) m_bDraw=TRUE; if(m_Progress1.m_hWnd && m_bDraw){ if(m_nValue<MAX){ m_nValue++; } m_Progress1.SetPos(m_nValue); UpdateData(FALSE); } return CDialog::WindowProc(message, wParam, lParam); }
ここで行っている計算処理は、
というものです。
まずは、この計算処理を行う専用のクラスを作ります。
このクラスが、ワーカースレッドで実際の処理を行うことになります。
#ifndef INCLUDE_CALC_H
#define INCLUDE_CALC_H
class CCalc{
int m_nValue,m_nMax;
public:
void init(int max){ //初期化
m_nValue=0;m_nMax=max;
}
void execute(){ //主処理
while(m_nValue<m_nMax){
m_nValue++;
//負荷のかかる処理
CString s;s.Format("%d",m_nValue);
}
}
int getValue() const{ //値取得
return m_nValue;
}
};
#endif //INCLUDE_CALC_H
init | 初期化 | 最大値の設定と、増加させる値(m_nValue)のクリア |
---|---|---|
execute | 主処理 | m_nValueの増加処理。while文の中で単純に加算している。 「負荷のかかる処理」は、 プログレスバーの動きを分かり易くする為の単なるウェイト |
getValue | 値取得 | m_nValueを返す |
executeの中を見て下さい。単純にwhile文で
最大値になるまでm_nValueを増加させているだけです。
普通なら、while文が終わるまで他の処理は実行できません。
しかし、これをワーカースレッドとすることにより、
インターフェーススレッドからはgetValueを使って
計算中の値を取得することが出来るのです。
さっそく、作ったクラスをワーカースレッドとして呼び出すようにしましょう。
ワーカースレッドの開始には、AfxBeginThreadを使います。
ワーカースレッド本体となる関数を別途定義し、AfxBeginThreadに渡す必要があります。
AfxBeginThreadはスレッドを作成し、渡された関数を呼び出すのです。
この関数は引数を持っていて、ポインタを渡すことが出来ます。
このポインタを利用して、処理したいオブジェクトを受け渡せます。
プロセス内ではスレッドが異なってもメモリ空間は同一なので、ポインタ値はそのまま使えます。
#include "calc.h" class CAboutDlg : public CDialog { 〜 private: CCalc m_Calc; BOOL m_bDraw; }; static UINT AFX_CDECL ThreadProcCalc(LPVOID pParam) { CCalc *cp=(CCalc*)pParam; cp->execute(); return 0; } const int MAX=10000; BOOL CAboutDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: この位置に初期化の補足処理を追加してください m_Progress1.SetRange(0,MAX); m_Calc.init(MAX); AfxBeginThread(ThreadProcCalc,&m_Calc); //ワーカースレッドの開始:m_Calc.execute()を意味する return TRUE; // コントロールにフォーカスを設定しないとき、戻り値は TRUE となります // 例外: OCX プロパティ ページの戻り値は FALSE となります }
マルチスレッド関連の関数が呼び出される順序は、
インターフェース | ワーカー | |
---|---|---|
CCalc::init | ||
↓ | ||
AfxBeginThread | → | ThreadProcCalc |
↓ | ↓ | |
OnInitDialogの 後続処理 | CCalc::execute |
です。
AfxBeginThreadが呼ばれた時点でワーカースレッドが作られ、ThreadProcCalcの実行が始まります。
また、AfxBeginThreadを呼び出した後のOnInitDialogの処理もそのまま続行されます。
この辺りがマルチスレッドのマルチスレッドたる所以で、慣れないと変な感じなんですが。
さて、ワーカースレッドを開始しただけではプログレスバーには反映されません。 プログレスバーの状況表示部分も変更します。
test.cpp:LRESULT CAboutDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください if(message==WM_PAINT) m_bDraw=TRUE; if(m_Progress1.m_hWnd && m_bDraw){ int value=m_Calc.getValue(); m_Progress1.SetPos(value); UpdateData(FALSE); } return CDialog::WindowProc(message, wParam, lParam); }
ワーカースレッドのexecuteで値を計算中なのですが、 getValueを使うことで計算中の値が取得でき、それをプログレスバーの表示に使っています。
ワーカースレッドは、AfxBeginThreadに渡した関数(今回の例ではThreadProcCalc)が
終了した時点で終了しますが、インターフェーススレッドは勝手に続いているため、
終わったかどうかを知る方法はありません。
CCalcクラスに計算が終了したかを訊ねる関数を追加したりするのが
とりあえず無難な方法と思われます。
class CCalc{ int m_nValue,m_nMax; BOOL m_bEnd; public: void init(int max){ //初期化 m_nValue=0;m_nMax=max; m_bEnd=FALSE; } 〜 void setEnd(){ m_bEnd=TRUE; } BOOL isEnd() const{ //終了したかどうか return m_bEnd; } };test.cpp:
static UINT AFX_CDECL ThreadProcCalc(LPVOID pParam)
{
CCalc *cp=(CCalc*)pParam;
cp->execute();
cp->setEnd();
return 0;
}
もしワーカースレッドの終了を待たずにインターフェーススレッドを終了する場合、
ただ単にインターフェーススレッドを終了すれば大丈夫っぽいです。
ただし、インターフェースとワーカーの双方で使っているオブジェクトを
片方で解放してしまうと、他方が使用しようとしてアクセス違反になることがあるので
注意が必要です。