マルチスレッドとは、スレッドを複数同時に動かすことです。普通のアプリケーションは
基本的に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;
}
もしワーカースレッドの終了を待たずにインターフェーススレッドを終了する場合、
ただ単にインターフェーススレッドを終了すれば大丈夫っぽいです。
ただし、インターフェースとワーカーの双方で使っているオブジェクトを
片方で解放してしまうと、他方が使用しようとしてアクセス違反になることがあるので
注意が必要です。