MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
MFCの基礎 - マルチスレッドのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
MFCの基礎 - マルチスレッド
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == マルチスレッドを使用することにより、以下に示すようなメリットが得られる。 * 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> == 作成手順 : ワーカースレッド == # ワーカースレッド制御関数の定義 #: まず、クラスに静的なワーカースレッド制御関数を定義する。 #: <br> #: ワーカースレッド制御関数は静的であるため、クラスのメンバ変数およびメンバ関数を直接使用することができない。 #: <u>ただし、<code>void*</code>型の引数を渡して特定のキャストを行うことにより、ワーカースレッド制御関数下でクラスのメンバを使用することができる。</u> #: <br> # ワーカースレッド本体の関数の定義 #: 次に、同じクラスに静的ではないワーカースレッド本体の関数を定義する。 #: ワーカースレッド本体の関数では、静的でないメンバ変数およびメンバ関数が使用できる。 #: <br> # スレッドの開始と終了処理 #: <code>AfxBeginThread</code>関数でスレッドを作成・開始し、適切な終了処理を実装する。 <br><br> == サンプルコード : ワーカースレッド == ==== ヘッダファイル ==== <syntaxhighlight lang="cpp"> // CFileView.hファイル class CFileView : public CDockablePane { private: CWinThread *m_pLoadXMLThread; // スレッドオブジェクトへのポインタ CCriticalSection m_csDataAccess; // クリティカルセクション (排他制御用) volatile bool m_bThreadRunning; // スレッド実行中フラグ public: // 静的なワーカースレッド制御関数 static UINT LoadXMLThreadFunc(void *pParam); // ワーカースレッド本体 void LoadXMLThreadFunc(); // メッセージハンドラ afx_msg LRESULT OnCompleteLoadXML(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() }; </syntaxhighlight> <br> ==== 実装ファイル ==== <syntaxhighlight lang="cpp"> // CFileView.cppファイル // メッセージマップ BEGIN_MESSAGE_MAP(CFileView, CDockablePane) ON_MESSAGE(WM_USER_COMPLETE_LOAD_XML, &CFileView::OnCompleteLoadXML) END_MESSAGE_MAP() // ボタンクリック時の処理 void CFileView::OnButtonClick() { // 既にスレッドが実行中の場合は何もしない if (m_bThreadRunning) { AfxMessageBox(_T("処理が実行中です。")); return; } // スレッド実行中フラグを設定 m_bThreadRunning = TRUE; // XML読み込み処理スレッド関数を実行 // CREATE_SUSPENDED : 一時停止状態でスレッドを作成 m_pLoadXMLThread = ::AfxBeginThread( LoadXMLThreadFunc, // スレッド関数 this, // パラメータ (thisポインタ) THREAD_PRIORITY_NORMAL, // スレッドの優先度 0, // スタックサイズ (0=デフォルト) CREATE_SUSPENDED, // 作成フラグ NULL // セキュリティ属性 ); ASSERT(m_pLoadXMLThread != NULL); if (m_pLoadXMLThread) { // メインウィンドウを設定 m_pLoadXMLThread->m_pMainWnd = this; // 自動削除を無効化 (手動でdeleteする) m_pLoadXMLThread->m_bAutoDelete = FALSE; // スレッド処理の開始 m_pLoadXMLThread->ResumeThread(); // ワーカースレッドが終了するまで待機 // メッセージポンプを回してUIの応答性を維持 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; } } // 静的なワーカースレッド制御関数 // 引数: この制御関数が定義されているクラスへのポインタ // 戻り値: スレッドの終了コード UINT CFileView::LoadXMLThreadFunc(void *pParam) { UINT nResult = 0; // void*からCWnd*にキャスト後、CFileView*にダイナミックキャスト CFileView *pFileView = dynamic_cast<CFileView*>(reinterpret_cast<CWnd*>(pParam)); 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 nResult; } // ワーカースレッド本体の関数 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> <br><br> == 画面の更新 == ワーカースレッド内において、<code>UpdateData</code> 関数等の画面を描画するような関数は実行することができない。<br> これは、MFCのUIコントロールがスレッドセーフではないためである。<br> <br> <code>AfxGetMainWnd</code> 関数の戻り値は、<code>m_pLoadXMLThread->m_pMainWnd</code> に渡したウィンドウである。<br> 設定しない場合は <code>NULL</code> となり、また、ダイアログの <code>Create</code> 関数も呼ぶことができない。<br> <br> そのため、ワーカースレッドの処理と同期して画面を更新する場合は、以下に示すようにワーカースレッド内からメインウィンドウにメッセージを送信する。<br> メインウインドウは、メッセージを受信して任意の処理を行う。<br> <br> ==== ユーザ定義メッセージの定義 ==== <syntaxhighlight lang="cpp"> // stdafx.hファイル //------------------------------------------------------------------------ // USER_MESSAGE //------------------------------------------------------------------------ #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> <br> ==== SendMessageとPostMessageの違い ==== * 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() { CSingleLock lock(&m_cs, TRUE); return m_strData; } }; </syntaxhighlight> <br> ==== CMutex ==== プロセス間の排他制御にも使用可能である。<br> これは、名前付きで作成することができる。<br> <br> <syntaxhighlight lang="cpp"> CMutex m_mutex(FALSE, _T("MyAppMutex")); void ThreadFunc() { CSingleLock lock(&m_mutex, TRUE); // 排他制御が必要な処理 } </syntaxhighlight> <br> ==== CEvent ==== スレッド間でイベントを通知する。<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 { private: 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> <br><br> == 注意事項 == ==== メモリリーク防止 ==== * <code>m_bAutoDelete = FALSE</code>の場合 *: 必ずスレッドオブジェクトを <code>delete</code> する。 * <code>m_bAutoDelete = TRUE</code>の場合 *: スレッド終了後に自動的に削除されるため、<code>delete</code> してはいけない。 <br> ==== UI操作の制限 ==== ワーカースレッドから直接UIを操作しない。<br> 必ずメッセージを使用する。<br> <br> ==== 例外処理 ==== スレッド内で発生した例外はメインスレッドに伝播しないため、スレッド内で適切に処理する。<br> <br> ==== デバッグ ==== マルチスレッドのデバッグは困難なため、<code>TRACE</code> マクロやログ出力を活用する。<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__ [[カテゴリ:MFC]]
MFCの基礎 - マルチスレッド
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse