📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

編集の要約なし
1行目: 1行目:
== 概要 ==
== 概要 ==
MFCアプリケーションにおいて、マルチスレッドを使用する方法について記述する。<br><br>
マルチスレッドを使用することにより、以下に示すようなメリットが得られる。
* UIの応答性向上
*: 時間の掛かる処理でもユーザインターフェースがフリーズしない。
* 並行処理の実現
*: 複数の処理を同時に実行し、処理時間を短縮できる。
* バックグラウンド処理
*: ファイルI/O、ネットワーク通信、データベースアクセス等を非同期で実行できる。
<br>
以下に示すような処理において、マルチスレッドの使用が効果的である。<br>
* 大量のファイルの読み込み・書き込み処理
* XMLやJSON等のデータのパース処理
* ネットワーク通信 (ダウンロード、アップロード)
* データベースへの大量データの挿入・更新
* 画像処理や動画処理等の計算量の多い処理
* 定期的な監視処理やポーリング処理
<br>
MFCでマルチスレッドを実装する場合の特徴は以下の通りである。<br>
* <code>AfxBeginThread</code> メソッドによりスレッドを作成することができる。
* <code>CWinThread</code> クラスでスレッドを管理する。
* UIコントロールは <u>スレッドセーフではない</u> ため、ワーカースレッドから直接操作できない。
* メインスレッドとの通信には <u>ウインドウメッセージ</u> を使用する。
* 共有リソースへのアクセスには <u>排他制御</u> が必要となる。
<br><br>
 
== スレッドの種類 ==
MFCでは、以下に示す2種類のスレッドを作成することができる。<br>
* ワーカースレッド
*: バックグラウンド処理を行うスレッド
*: メッセージループを持たず、時間の掛かる処理 (ファイルI/O、計算処理等) に使用する。
*: <br>
* UIスレッド
*: 独自のメッセージループを持つスレッド
*: ウインドウやダイアログを扱う場合に使用する。
*: 複数のウインドウを独立して動作させる場合等に使用する。
<br><br>


== 作成手順 ==
== 作成手順 : ワーカースレッド ==
# ワーカースレッド制御関数の定義
# ワーカースレッド制御関数の定義
#: まず、クラスに静的なワーカースレッド制御関数を定義する。
#: まず、クラスに静的なワーカースレッド制御関数を定義する。
12行目: 46行目:
#: 次に、同じクラスに静的ではないワーカースレッド本体の関数を定義する。
#: 次に、同じクラスに静的ではないワーカースレッド本体の関数を定義する。
#: ワーカースレッド本体の関数では、静的でないメンバ変数およびメンバ関数が使用できる。
#: ワーカースレッド本体の関数では、静的でないメンバ変数およびメンバ関数が使用できる。
#: <br>
# スレッドの開始と終了処理
#: <code>AfxBeginThread</code>関数でスレッドを作成・開始し、適切な終了処理を実装する。
<br><br>
<br><br>


== サンプルコード ==
== サンプルコード : ワーカースレッド ==
==== ヘッダファイル ====
  <syntaxhighlight lang="cpp">
  <syntaxhighlight lang="cpp">
  // CFileView.hファイル
  // CFileView.hファイル
   
   
  class CFileView
  class CFileView : public CDockablePane
  {
  {
     private:
     private:
       CWinThread *m_pLoadXMLThread
       CWinThread     *m_pLoadXMLThread;    // スレッドオブジェクトへのポインタ
      CCriticalSection m_csDataAccess;      // クリティカルセクション (排他制御用)
      volatile bool  m_bThreadRunning;    // スレッド実行中フラグ
   
   
     public:
     public:
       static UINT LoadXMLThreadFunc(void *pParam); // 静的なワーカースレッド制御関数
      // 静的なワーカースレッド制御関数
       void       LoadXMLThreadFunc();             // ワーカースレッド本体
       static UINT LoadXMLThreadFunc(void *pParam);
   
      // ワーカースレッド本体
       void LoadXMLThreadFunc();
      // メッセージハンドラ
      afx_msg LRESULT OnCompleteLoadXML(WPARAM wParam, LPARAM lParam);
      DECLARE_MESSAGE_MAP()
  };
  };
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
  <syntaxhighlight lang="c++">
==== 実装ファイル ====
  <syntaxhighlight lang="cpp">
  // CFileView.cppファイル
  // CFileView.cppファイル
   
   
// メッセージマップ
BEGIN_MESSAGE_MAP(CFileView, CDockablePane)
    ON_MESSAGE(WM_USER_COMPLETE_LOAD_XML, &CFileView::OnCompleteLoadXML)
END_MESSAGE_MAP()
// ボタンクリック時の処理
  void CFileView::OnButtonClick()
  void CFileView::OnButtonClick()
  {
  {
    // 既にスレッドが実行中の場合は何もしない
    if (m_bThreadRunning) {
      AfxMessageBox(_T("処理が実行中です。"));
      return;
    }
    // スレッド実行中フラグを設定
    m_bThreadRunning = TRUE;
     // XML読み込み処理スレッド関数を実行
     // XML読み込み処理スレッド関数を実行
     m_pLoadXMLThread = ::AfxBeginThread(LoadXMLThreadFunc, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL);
    // CREATE_SUSPENDED : 一時停止状態でスレッドを作成
     m_pLoadXMLThread = ::AfxBeginThread(
      LoadXMLThreadFunc,           // スレッド関数
      this,                       // パラメータ (thisポインタ)
      THREAD_PRIORITY_NORMAL,     // スレッドの優先度
      0,                           // スタックサイズ (0=デフォルト)
      CREATE_SUSPENDED,           // 作成フラグ
      NULL                        // セキュリティ属性
    );
    ASSERT(m_pLoadXMLThread != NULL);
   
   
     ASSERT(m_pLoadXMLThread);
     if (m_pLoadXMLThread) {
      // メインウィンドウを設定
      m_pLoadXMLThread->m_pMainWnd = this;
   
   
    if(m_pLoadXMLThread) {
      // 自動削除を無効化 (手動でdeleteする)
      m_pLoadXMLThread->m_pMainWnd = this;
       m_pLoadXMLThread->m_bAutoDelete = FALSE;
       m_pLoadXMLThread->m_bAutoDelete = FALSE;
   
   
       // スレッド処理の開始
       // スレッド処理の開始
       m_pLoadXMLThread->ResumeThread();
       m_pLoadXMLThread->ResumeThread();
       while (WaitForSingleObject(m_pLoadXMLThread->m_hThread, 100) == WAIT_TIMEOUT) {
          // ワーカースレッドが終了するまで待機
      // ワーカースレッドが終了するまで待機
      // メッセージポンプを回してUIの応答性を維持
       while (::WaitForSingleObject(m_pLoadXMLThread->m_hThread, 100) == WAIT_TIMEOUT) {
           MSG msg;
           MSG msg;
           while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
           while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
54行目: 131行目:
       }
       }
   
   
      // スレッドオブジェクトを削除
       delete m_pLoadXMLThread;
       delete m_pLoadXMLThread;
      m_pLoadXMLThread = NULL;
      m_bThreadRunning = FALSE;
     }
     }
    // マルチスレッドの応用(ウェイトウィンドウを表示する)
    //m_pLoadXMLThread = ::AfxBeginThread(LoadXMLThreadFunc, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL);
    //if(m_pLoadXMLThread) {
    //  CRect rc;
    //  HWND hNotifyWnd = CreateDialog(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDD_PROCESSING), this->m_hWnd, (DLGPROC)YCommonProc);
    //  ::ShowWindow(hNotifyWnd, SW_SHOW);
    //  ::GetClientRect(&rc);
    //  ::MoveWindow(hNotifyWnd, (rc.right - rc.left) / 2 - 100, (rc.bottom - rc.top) / 2 - 25, 200, 50, TRUE);
    //  m_pLoadXMLThread->m_pMainWnd = this;
    //  m_pLoadXMLThread->m_bAutoDelete = FALSE;
    //  m_pLoadXMLThread->ResumeThread();
    //  while(::WaitForSingleObject(m_pLoadXMLThread->m_hThread, 100) == WAIT_TIMEOUT) {
    //      // ワーカースレッドが終了するまで待機
    //      MSG msg;
    //      while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
    //        ::TranslateMessage(&msg);
    //        ::DispatchMessage(&msg);
    //      }
    //  }
    //  delete m_pLoadXMLThread;
    //  ::DestroyWindow(hNotifyWnd);
    //}
  }
  }
   
   
  // 静的なワーカースレッド制御関数
  // 静的なワーカースレッド制御関数
  // 引数 : この制御関数が定義されているクラスへのポインタ
  // 引数: この制御関数が定義されているクラスへのポインタ
  // 実処理はワーカースレッド本体の関数で行う
  // 戻り値: スレッドの終了コード
  UINT CFileView::LoadXMLThreadFunc(void *pParam)
  UINT CFileView::LoadXMLThreadFunc(void *pParam)
  {
  {
    UINT nResult = 0;
   
    // void*からCWnd*にキャスト後、CFileView*にダイナミックキャスト
     CFileView *pFileView = dynamic_cast<CFileView*>(reinterpret_cast<CWnd*>(pParam));
     CFileView *pFileView = dynamic_cast<CFileView*>(reinterpret_cast<CWnd*>(pParam));
     if(pFileView) {
   
       pFileView->LoadXMLThreadFunc();
     if (pFileView) {
       try {
          // 実処理はワーカースレッド本体の関数で行う
          pFileView->LoadXMLThreadFunc();
          nResult = 0; // 正常終了
      }
      catch (CException *e) {
          // 例外処理
          TCHAR szError[512];
          e->GetErrorMessage(szError, 512);
          TRACE(_T("スレッドエラー: %s\n"), szError);
          e->Delete();
          nResult = 1; // エラー終了
      }
      catch (...) {
          TRACE(_T("スレッドで不明なエラーが発生しました。\n"));
          nResult = 1; // エラー終了
      }
     }
     }
   
   
     return THREAD_EXIT_CODE;
     return nResult;
  }
  }
   
   
  // ワーカースレッド本体の関数
  // ワーカースレッド本体の関数
// スレッドの開始
  void CFileView::LoadXMLThreadFunc()
  void CFileView::LoadXMLThreadFunc()
  {
  {
     // ワーカースレッドで実行する処理
     // ワーカースレッドで実行する処理
    // 例: XMLファイルの読み込み
    CString firstFilePath = _T("C:\\Data\\sample.xml");
    int firstIndex = 0;
    // 時間の掛かる処理を実行
     // ...略
     // ...略
    // 共有データへのアクセス時は排他制御を行う
    {
      CSingleLock lock(&m_csDataAccess, TRUE);
      // クリティカルセクション内でデータを操作
      // ...略
    } // lock変数のスコープを抜けると自動的にアンロック
    // XMLファイルの読み込みが完了したメッセージを送信
    // SendMessageは同期的に処理される (戻り値を受け取れる)
    AfxGetMainWnd()->SendMessage(
      WM_USER_COMPLETE_LOAD_XML,
      (WPARAM)&firstIndex,
      (LPARAM)firstFilePath.GetString()
    );
}
// メッセージハンドラ: XML読み込み完了時の処理
LRESULT CFileView::OnCompleteLoadXML(WPARAM wParam, LPARAM lParam)
{
    int *pIndex = reinterpret_cast<int*>(wParam);
    LPCTSTR pFilePath = reinterpret_cast<LPCTSTR>(lParam);
    // UI更新が可能 (メインスレッドで実行される)
    UpdateData(TRUE);
    // 処理完了のメッセージを表示
    CString msg;
    msg.Format(_T("XMLファイルの読み込みが完了しました。\nIndex: %d\nPath: %s"), *pIndex, pFilePath);
    AfxMessageBox(msg);
    UpdateData(FALSE);
    return 0;
}
</syntaxhighlight>
<br>
==== ウェイトダイアログを表示する例 ====
<syntaxhighlight lang="cpp">
// CFileView.cppファイル
void CFileView::OnButtonClickWithWaitDlg()
{
    if (m_bThreadRunning) {
      return;
    }
    m_bThreadRunning = TRUE;
    m_pLoadXMLThread = ::AfxBeginThread(
      LoadXMLThreadFunc, this,
      THREAD_PRIORITY_NORMAL, 0,
      CREATE_SUSPENDED, NULL
    );
    if (m_pLoadXMLThread) {
      // ウェイトダイアログを表示
      CRect rc;
      GetClientRect(&rc);
      HWND hNotifyWnd = ::CreateDialog(
          AfxGetInstanceHandle(),
          MAKEINTRESOURCE(IDD_PROCESSING),
          this->m_hWnd,
          (DLGPROC)YCommonProc
      );
      ::ShowWindow(hNotifyWnd, SW_SHOW);
      ::MoveWindow(
          hNotifyWnd,
          (rc.right - rc.left) / 2 - 100,
          (rc.bottom - rc.top) / 2 - 25,
          200, 50,
          TRUE
      );
      m_pLoadXMLThread->m_pMainWnd = this;
      m_pLoadXMLThread->m_bAutoDelete = FALSE;
      m_pLoadXMLThread->ResumeThread();
      // メッセージポンプを回しながらスレッドの終了を待つ
      while (::WaitForSingleObject(m_pLoadXMLThread->m_hThread, 100) == WAIT_TIMEOUT) {
          MSG msg;
          while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
          }
      }
      delete m_pLoadXMLThread;
      m_pLoadXMLThread = NULL;
      m_bThreadRunning = FALSE;
      // ウェイトダイアログを閉じる
      ::DestroyWindow(hNotifyWnd);
    }
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
109行目: 283行目:


== 画面の更新 ==
== 画面の更新 ==
ワーカースレッド内において、<code>UpdateData</code>関数等の画面を描画するような関数は実行することができない。<br>
ワーカースレッド内において、<code>UpdateData</code> 関数等の画面を描画するような関数は実行することができない。<br>
これは、MFCのUIコントロールがスレッドセーフではないためである。<br>
<br>
<br>
<code>AfxGetMainWnd</code>関数の戻り値は、m_pLoadXMLThread->m_pMainWndに渡したウィンドウである。<br>
<code>AfxGetMainWnd</code> 関数の戻り値は、<code>m_pLoadXMLThread->m_pMainWnd</code> に渡したウィンドウである。<br>
設定しない場合は<code>NULL</code>となり、また、ダイアログの<code>Create</code>関数も呼ぶことができない。<br>
設定しない場合は <code>NULL</code> となり、また、ダイアログの <code>Create</code> 関数も呼ぶことができない。<br>
<br>
<br>
そのため、ワーカースレッドの処理と同期して画面を更新する場合 (UpdateData(TRUE)をスレッドから同期して実行する場合) は、<br>
そのため、ワーカースレッドの処理と同期して画面を更新する場合は、以下に示すようにワーカースレッド内からメインウィンドウにメッセージを送信する。<br>
以下に示すようにワーカースレッド内からメインウィンドウにメッセージを送信する。<br>
メインウインドウは、メッセージを受信して任意の処理を行う。<br>
メインウインドウは、メッセージを受信して任意の処理を行う。<br>
<br>
<br>
==== ユーザ定義メッセージの定義 ====
  <syntaxhighlight lang="cpp">
  <syntaxhighlight lang="cpp">
  // stdafx.hファイル
  // stdafx.hファイル
124行目: 299行目:
  // USER_MESSAGE
  // USER_MESSAGE
  //------------------------------------------------------------------------
  //------------------------------------------------------------------------
  #define WM_USER_COMPLETE_LOAD_XML  (WM_USER + 1)  // XMLデータの読み込みの完了メッセージ
  #define WM_USER_COMPLETE_LOAD_XML  (WM_USER + 1)  // XMLデータ読み込み完了
#define WM_USER_PROGRESS_UPDATE    (WM_USER + 2)  // 進捗状況更新
#define WM_USER_ERROR_OCCURRED      (WM_USER + 3)  // エラー発生
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
  <syntaxhighlight lang="c++">
==== SendMessageとPostMessageの違い ====
  // CFileView.cppファイル
* SendMessage
*: 同期的にメッセージを送信する。
*: メッセージハンドラの処理が完了するまで制御が戻らない。
*: 戻り値を受け取ることができる。
*: <br>
* PostMessage
*: 非同期的にメッセージを送信する。
*: メッセージキューに追加後、即座に制御が戻る。
*: 戻り値を受け取ることができない。
<br>
<u>※注意</u><br>
<u>スレッドの終了を待機する <code>WaitForSingleObject</code> 関数を使用する場合、<code>SendMessage</code> でデッドロックになることに注意する。</u><br>
これは、<code>SendMessage</code> が同期的であるため、メインスレッドが <code>WaitForSingleObject</code> で待機中にワーカースレッドからの<code>SendMessage</code>を処理できないためである。<br>
<br>
これを解決するには、以下に示す方法がある。<br>
* 上記のサンプルコードのように、<code>WaitForSingleObject</code> 中にメッセージポンプを回す。
* <code>PostMessage</code> を使用して非同期でメッセージを送信する。
* スレッド終了時の処理をメッセージハンドラで行う。
<br><br>
 
== 排他制御 ==
複数のスレッドから同じデータにアクセスする場合、排他制御が必要である。<br>
MFCでは以下のクラスを使用して排他制御を行う。<br>
<br>
==== CCriticalSection ====
単一プロセス内での排他制御に使用。最も高速。<br>
  <syntaxhighlight lang="cpp">
  class CMyClass
{
private:
    CCriticalSection m_cs;
    CString m_strData;
public:
    void SetData(const CString &str)
    {
      CSingleLock lock(&m_cs, TRUE);  // コンストラクタでロック
      m_strData = str;
      // スコープを抜けるとデストラクタで自動的にアンロック
    }
   
   
// まず、ワーカースレッドからメイン処理にメッセージを送信する
    CString GetData()
// 次に、メッセージを受信して任意の処理を行う
    {
BEGIN_MESSAGE_MAP(CFileView, CDockablePane)
      CSingleLock lock(&m_cs, TRUE);
     // UserMessage
      return m_strData;
    ON_MESSAGE(WM_USER_COMPLETE_LOAD_XML, &CFileView::OnCompleteLoadXML)
     }
END_MESSAGE_MAP()
};
</syntaxhighlight>
<br>
==== CMutex ====
プロセス間の排他制御にも使用可能である。<br>
これは、名前付きで作成することができる。<br>
<br>
<syntaxhighlight lang="cpp">
CMutex m_mutex(FALSE, _T("MyAppMutex"));
   
   
  // ワーカースレッド関数の本体にて
  void ThreadFunc()
  // XMLファイルの読み込みが完了したメッセージを送信
  {
AfxGetMainWnd()->SendMessage(WM_USER_COMPLETE_LOAD_XML, (WPARAM)&firstIndex, (LPARAM)firstFilePath.GetString());
    CSingleLock lock(&m_mutex, TRUE);
    // 排他制御が必要な処理
}
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
  <syntaxhighlight lang="c++">
==== CEvent ====
  // CFileView.hファイル
スレッド間でイベントを通知する。<br>
<br>
  <syntaxhighlight lang="cpp">
CEvent m_eventComplete(FALSE, TRUE); // 手動リセット、初期状態非シグナル
// ワーカースレッド内
void WorkerThread()
{
    // 処理実行
    // ...
    // 処理完了を通知
    m_eventComplete.SetEvent();
}
   
   
// メインスレッド
void MainThread()
{
    // 処理完了を待機
    ::WaitForSingleObject(m_eventComplete.m_hObject, INFINITE);
}
</syntaxhighlight>
<br><br>
== スレッドの終了 ==
==== 正常な終了方法 ====
* スレッド関数からreturnする (推奨)
* <code>AfxEndThread</code> を呼び出す
* <code>TerminateThread</code> は使用しない。 (強制終了でリソースリークの原因)
<br>
==== スレッドの中断処理 ====
<syntaxhighlight lang="cpp">
  class CFileView
  class CFileView
  {
  {
     // 追加
private:
     afx_msg LRESULT OnCompleteLoadXML(WPARAM wParam, LPARAM lParam );
     volatile bool m_bStopThread;  // スレッド停止フラグ
  }
public:
    void StopThread()
     {
      m_bStopThread = TRUE;
      if (m_pLoadXMLThread) {
          // スレッドの終了を待つ (タイムアウト付き)
          DWORD dwRet = ::WaitForSingleObject(
            m_pLoadXMLThread->m_hThread,
            5000  // 5秒でタイムアウト
          );
          if (dwRet == WAIT_TIMEOUT) {
            TRACE(_T("警告: スレッドが時間内に終了しませんでした。\n"));
          }
      }
    }
    void LoadXMLThreadFunc()
    {
      m_bStopThread = FALSE;
      for (int i = 0; i < 1000; i++) {
          // 定期的に停止フラグをチェック
          if (m_bStopThread) {
            TRACE(_T("スレッドを中断しました。\n"));
            return;
          }
          // 処理実行
          // ...
      }
    }
  };
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
== 注意事項 ==
==== メモリリーク防止 ====
* <code>m_bAutoDelete = FALSE</code>の場合
*: 必ずスレッドオブジェクトを <code>delete</code> する。
* <code>m_bAutoDelete = TRUE</code>の場合
*: スレッド終了後に自動的に削除されるため、<code>delete</code> してはいけない。
<br>
<br>
後は、SendMessage関数を受け取った側で処理を行えばよい。<br>
==== UI操作の制限 ====
受信側では、UpdateData(TRUE)、および、UpdateData(FALSE)等を呼ぶことができる。<br>
ワーカースレッドから直接UIを操作しない。<br>
必ずメッセージを使用する。<br>
<br>
<br>
<u>この時、スレッドの終了を待機するWaitForSingleObject関数を使用する場合、デッドロックになることに注意する。</u><br>
==== 例外処理 ====
スレッドの終了時に何らかの処理を行うのであれば、上記のようにスレッド終了時に"メッセージをメインウィンドウへ送信する"ことにより解決できる。<br>
スレッド内で発生した例外はメインスレッドに伝播しないため、スレッド内で適切に処理する。<br>
<br>
==== デバッグ ====
マルチスレッドのデバッグは困難なため、<code>TRACE</code> マクロやログ出力を活用する。<br>
<br>
==== スレッド数の制限 ====
過度なスレッド作成はパフォーマンス低下の原因となる。<br>
必要に応じてスレッドプールを検討する。<br>
<br><br>
<br><br>


{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}


__FORCETOC__
__FORCETOC__
[[カテゴリ:MFC]]
[[カテゴリ:MFC]]