スレッドの安全な終了のさせ方

このQ&Aのポイント
  • サブスレッドの安全な終了方法とは?
  • イベントオブジェクトを使用して安全な終了処理を行う
  • SendMessageの使用を検討し、デストロイ時にCloseHandleを実行する
回答を見る
  • ベストアンサー

スレッドの安全な終了のさせ方

スレッドの安全な終了のさせ方  メインスレッドにてCreateThread命令を使い、あるサブスレッドを作りました。 このサブスレッドは内部でmallocを使い動的に配列領域を確保して その配列領域をforループ等で「かなり時間の掛かる処理」として繰り返し アクセスしています。 ループが終了した時に「free」を実行してmalloc領域を開放しています。 アプリ終了時にメインスレッドからこのサブスレッドを終了させるのに メインウインドウにWM_DESTROYメッセージが送られた時、これまで単に そこで「CloseHandle(hSubThread);」とだけ書いていたのですが、 もしかしたらこれでは場合によっては(サブスレッドがループ処理中だったら) malloc領域が開放されずにリークしてしまうのではないかと思いました。  そこでイベントオブジェクトを使い、サブスレッドがループ処理中の 時には非シグナル状態にして、ループが終了しfreeで領域を開放した後 シグナル状態にするということにして、メインスレッドはそれを WaitForSingleObjectで待つという構造にしました。 ところが「メインスレッドに待ちを作るな」という言葉通り、これでは 上手く行きませんでした。サブスレッドはその時間の掛かる処理の 最中でSendMassage等でメインスレッドの処理を促すような命令を (例えばその処理の進捗状況を表示するなど)を幾つも行っていたので、 もしWaitFor~でメインを待たせると「サブスレッドの処理も進まなくなり 結果両方がロックして動かなくなってしまう」という悲しい状況に 嵌ってしまうのです。 SendMessageを徹底的に無くすということも考えたのですが、 (例えばPostMessageに書き換えるなどもやってみたのですが、これは 全く意図した動作をしてくれない場合もあり)、別の方法では どうしても代替できないケースもあって、全て消すというのは 現実的ではないのかもと。。  このようなサブスレッドを安全に終了させるにはどうしたら良いでしょうか? あるいは単にデストロイ時にCloseHandleとするだけでも良いのでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.4

>SetEvent(hEventObject1);//イベントオブジェクトをシグナルに スレッド終了を判断する場合はスレッドのハンドル自身を見た方が確実です。 HANDLE thread_handle = ::CreateThread(略); (略) ::WaitForSingleObject( thread_handle , INFINITE ); スレッドは終了時にハンドルがシグナル状態になります。 >SendMessage(hMainWnd,....); >//メインウインドウに何かのメッセージを送信 >//なってた時に処理が進まなくなる。 名前から察するにhMainWndはメインスレッドで動いているようですが そのメインスレッドの処理がWaitForSingleObjectによって止まっているのなら処理は返ってきません。 つまりサブスレッドがメインスレッドとなんらかのやりとりをしたいなら、 この時点でメイン側はWaitForSingleObjectで待ってはいけません。 1. Main -> Subに終了前準備しろと通知 2. Sub -> Mainに終了前準備完了を通知 3. Main -> Subに終了しろと通知 4. MainはSubが終了するのをWaitForSingleObjectで待つ。

jacoby2200
質問者

お礼

>つまりサブスレッドがメインスレッドとなんらかのやりとりをしたいなら、 >この時点でメイン側はWaitForSingleObjectで待ってはいけません。 >1. Main -> Subに終了前準備しろと通知 >2. Sub -> Mainに終了前準備完了を通知 >3. Main -> Subに終了しろと通知 >4. MainはSubが終了するのをWaitForSingleObjectで待つ。  解説ありがとうございます。 「段階に分けて」終了処理を行うんですね。 それを受けてメインスレッドのコールバックプロシージャのイベント処理を 書き直しました。 まず「1. Main -> Subに終了前準備しろと通知」 をWM_DESTROYイベントで行っては、そのままメインスレッドが サブの終了を待てずに終わってしまうので その一段階前にWM_CLOSEイベントでこの通知を行い、 この通知を受け取ったサブはスレッドのループを抜けて メインへ「PostMessage」でExit準備完了を通知 (ここでSendMessageを使うと元も子もないので)。 (MY_WM_SUBTHREAD_READY_TO_EXITをメインへポスト)。 メインでMY_WM_SUBTHREAD_READY_TO_EXITを受け取るとそこで WaitForSingleObject(hSubThread,INFINITE); でスレッド終了を待つ。 スレッドが終了したのち、スレッドのハンドルをクローズし DestroyWindowで終了する。 これで無事終了させることができました。 それにしても、段階に分けて終了処理を行うというのはとても 賢明な方法ですね。 教えていただいて、"なるほど"、と初めて気が付きました。 お陰様でプログラムを安心して終了させられそうです。 sha-girlさん、回答ありがとうございました。またよろしくお願いします。 ---------(code)----------- case WM_CLOSE: MessageBox(hWnd,"サブスレッドEXITメッセージポスト。","終了処理",MB_OK); PostThreadMessage(subThread_ID,MY_TM_ABORT_THREAD,0,0);//スレッド中断メッセージのポスト return TRUE; case MY_WM_SUBTHREAD_READY_TO_EXIT: MessageBox(hWnd,"サブスレッドEXIT準備完了を受信。","終了処理",MB_OK); WaitForSingleObject(hSubThread,INFINITE); CloseHandle(hSubThread); MessageBox(hWnd,"スレッド終了処理が完了しました。","終了処理",MB_OK); DestroyWindow(hWnd); return TRUE; ---------(code end)-----------

その他の回答 (3)

  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.3

>「メインスレッドに待ちを作るな」 誰の言葉だかは知りませんがそれは実行中の話だと思います。 マルチスレッドでパフォーマンスを発揮するには如何に非同期で動かすかが重要になってきますが 今回はアプリ終了の際の話ですよね。 1.メインスレッドがサブスレッドに終了する事を通知。  (スマートではないですが例えばグローバルのint型の終了通知フラグで構いません。  Interlocked~系等のAPIを使ってもよいです。) 2.サブスレッドは自ら終了処理。 3.メイン側はWaitForSingleObject/WaitForMultipleObjectでサブスレッド終了を待つ。 4.WinMain(メインスレッド)から抜ける。 でなんら問題ないと思います。 ※これで終了しないのであれば、サブスレッドが終了できていない可能性があります。

参考URL:
http://msdn.microsoft.com/ja-jp/library/cc429227.aspx
jacoby2200
質問者

補足

sha-girlさん、tancoroさん、pdragonさん、回答有難うございます。 サブスレッドには終了を知らせるためにメインからPostThreadMessageで 終了メッセ-ジを投げています。サブスレッドでのループ内でそれを PeekMessageで拾ってループを抜けるようにしています。 (恐らくグローバル変数を用意する方法も形としては同様になるのでは ないかと思うのですが) サブスレッドの処理 ----------------------(code)------------------------- ResetEvent(hEventObject1);//イベントオブジェクトを非シグナルに(スレッド実行中のサイン) for (i=0;i<1000;i++){ ret=PeekMassage(&msg,0,0,0,PM_REMOVE); if (ret!=FALSE){ switch (msg.message){ case MY_TM_ABORT_THREAD://スレッド中断メッセージを受信 //スレッド中断。forループを抜ける } } //* //*ここで時間の掛かる処理を行う。 //* SendMessage(hMainWnd,....); //メインウインドウに何かのメッセージを送信 //↑しかしここでSendMessageを行うと、もしメインがWaitFor~待ちに //なってた時に処理が進まなくなる。 }//for(i)ループ終わり SetEvent(hEventObject1);//イベントオブジェクトをシグナルに ----------------------(code end)----------------------- もちろんループ中、PeekMessageをもっと細かい間隔で行えば待ち時間は 短くなるのですがそうするとループ処理そのものが時間がかかるように なるかと思い、出来るだけ間隔を取って拾う形にしています。 メインの方では終了時こうしています。 ----------------(code)-------------------- case WM_DESTROY: WaitForSingleObject(hEventObject1,10000); //↑ここで待つといつまでも(ここでは仮に10秒限度で)スレッド内のSendMessageが処理できない。 CloseHandle(hSubThread);//スレッドハンドルのクローズ ----------------(code end)---------------- もしメモリリークは気にしなくても良いということなら これらは必要ないのかもとも思いますが。。

  • tancoro
  • ベストアンサー率52% (11/21)
回答No.2

まず、1点目なんですが、 > もしかしたらこれでは場合によっては(サブスレッドがループ処理中だったら)malloc領域が開放されずにリークしてしまうのではないかと思いました。 これに関しては、リークする事はないと思います。メインスレッドもサブスレッドも1つのプロセス下で実行されているに過ぎず、プロセスが終了すれば当然その配下のスレッドで確保されたヒープ領域も解放されます。 しかし・・・・ ループ処理中に強制的にスレッドを終了させるのはあまり良いやり方ではありませんね。 そこで、2点目として綺麗に終わらせるやり方を考える。 まぁ、やり方はいろいろ考えられますが、下記はその1つの例として伺えて下さい。 1.メインスレッドからもサブスレッドからもアクセスできるメモリ領域(ヒープ領域か静的領域)を確保する。例えば、int isDestroy = 0;などとする。 2.メインスレッドにWM_DESTROYメッセージが来たらisDestroyの値をスレッドセーフで1に変更する。 3.サブスレッドでは、ループ処理の中でこのisDestroyを毎回チェックし、値が1であればループを終了するようにする。 このような感じでやるのが一番簡単な方法じゃないでしょうか。 ご参考までに。

jacoby2200
質問者

補足

上記sha-girlさんの欄に自分が書いた補足に訂正があります。 以下のWM_DESTROYのイベントで PostThreadMessageが抜けていました。訂正します。 メインの方では終了時こうしています。 ----------------(code)-------------------- case WM_DESTROY: PostThreadMessage(subThread_ID,MY_TM_ABORT_THREAD,0,0);//スレッド中断メッセージのポスト WaitForSingleObject(hEventObject1,10000); //↑ここで待つといつまでもサブスレッド内のSendMessageが処理できない。 CloseHandle(hSubThread);//スレッドハンドルのクローズ ----------------(code end)---------------- 失礼しました。

  • pdragon
  • ベストアンサー率35% (5/14)
回答No.1

プロセス終了時ならメモリリークは気にしなくてもいいと思いますし、 メイン側での子スレッドのCloseHandleは、子スレッドの動作とは 関係ないでしょうから、「終了を同期させる」ということで。 タイムアウト付きのWaitForSingleObject()のビジーループ内で::Sleep(0)でも 入れればメッセージ処理はされるんじゃないでしょうか。 単に終了を待つだけなら、イベントを使わずとも、GetExitCodeThread()を ビジーループ(::Sleep(100)あたり入れて)回すだけでも良いような気がしますが。

関連するQ&A

  • スレッドの終了の仕方

    こんばんわ 今スレッドを使ったプログラムを組んでいるのですが、_beginthreadexで起動したスレッド(無限ループ)二つをある条件の時にmainで終了させたいのですが、_endthreadでは特定のスレッドを終了させるようなパラメータが無いみたいなのでどうしていいかわかりません。CloseHandleだけでも終了させることができるのでしょうか??何かいい方法は無いでしょうか? windowsプログラム初心者なので分かりにくい質問で申し訳ないです。

  • マルチスレッド:スレッドの終了を検知する(Ruby)

    2つ以上のスレッドを生成・動作させ、それらが終了した時点で、メインに処理を移すといったことをRubyでしたいと思っています。 (スレッドが1つならば、メインをstopさせておき、スレッドの終了時に例外処理などを使ってrunさせる方法は思いつくのですが。。) 一つ考えたのは・・・ カウンタ変数をつくり、一つのスレッドが終了したらその値を増やす。 メインではwhileループによってその値を常に数えて、全スレッドの終了を把握する。 ただ無駄にwhileループを回してしまいます。。 一般的な効率の良い手段等あるのでしょうか?

  • スレッドについて

    スレッドについて勉強中なのですが、簡単なスレッド作り、 スレッドの処理が終わってからメインの処理を行わせたくて 以下のようなプログラムを書いてみました。 そこで、WaitForSingleObjectを使ってスレッドが終了するのを 待ちたいのですが、WaitForSingleObjectではまってしまうようで sprintfのメッセージ(スレッドが止まってしまっている)が 表示されません。 WRITE関数の処理が終われば、、WaitForSingleObjectで処理が 戻ってくると考えているのですが…。 スレッドについてと、間違いについて教えてもらえないでしょうか? 宜しくお願いします。 main { if(!stop) {   File = CreateFile(...); g_hThead = CreateThread(NULL,0,WRITE,(LPVOID)NULL,0,NULL); g_stop = false; }else { g_stop = true; ::WaitForSingleObject(g_hThead, INFINITE); CloseHandle(File); CloseHandle(hThead ); } } void WRITE() { while(!g_stop) { WriteFile(...); } ...処理を行う sprintf("スレッドを終了します。") }

  • VC++スレッドの正しい終了のさせかた

    VC++6.0にてAfxBeginThreadで m_bAutoDelete = TRUEにてスレッドをおこしております。 この終了時に制御関数のwhileループを脱する様にし、 正常にスレッドを終了させているつもりです。 この後、再度(アプリは継続して起動したまま) AfxBeginThreadにて全く同じ処理で再開すると、 なぜか、前のスレッドが未だ動作しているかのごとく 制御関数内のTRACEが2重に出力されます。 再度、停止し、またスレッド起動すると、 今度は3重になったかの様な動作をします。 スレッドが正しく終了されていないのでは?と思った現象として、 1回起動時にアプリを終了させると正常終了しますが、 2回起動以上は必ずスレッドのメモリーリークが出ます。 メモリーリーク個所はAfxBeginThreadでした。 制御関数内で必要ないとは思いましたが、終了時に AfxEndThreadを使用しましたが現象は同じでした。 そこで質問です。 1)この現象は、スレッドが正常に終了されていない事に起因しているのでしょうか? 2)スレッドを正しく終了させるにはどうすればいいのでしょうか? 当方、制御関数がループを抜け、さらにm_bAutoDelete = TRUEであれば オブジェクトも自動的に破棄されると思っていたのですが。。。 以上、よろしくお願いします。

  • スレッドを再生成する方法

    VBからC言語DLLを呼ぶプログラムを作っています。 DLLでスレッドを生成しているのですが、 VBから1回目の呼び出しではうまく動作するのですが、2回目はスレッドが生成されません。 2回目は1回目のスレッドが終了してから呼び出しているのですがなにか処理が必要なのでしょうか? スレッドは_beginthreadexで生成してスレッド関数内のreturnで終了しています。 closehandleを追加してみましたが駄目でした。

  • C#でスレッド実行中のイベントについて

    メインフォームの「処理開始ボタン」をクリックすると別スレッドが起動して、そのスレッド中で重たい処理をさせ、処理中に「中断ボタン」をクリックすると中断してアイドル状態に戻る様なプログラムについてですが、 先ず、Invokeを使わずに直接スレッドを起動すると期待通りの動作となり、Thread.Sleep()中でもボタンクリックのイベントが発生します。 しかし、Invokeとデリゲートを使ったスレッドを起動させるとスレッドの処理が終了するまでイベントが発生せず、行ったきり状態になってしまいます。 処理ループ内にAplication.DoEvent()を入れるとイベントが発生する様になりますが、Thread.Sleep()中はフリーズ状態となります。 Invokeを使ったスレッドでも、Invokeを使わない場合と同じ動作をさせる方法があれば教えて頂けないでしょうか。 どうぞ宜しくお願いします。

  • スレッドについて

    JAVAでプログラムを書く上で、Aという処理が終了次第、Bという処理を実行したい場合どうすればいいのでしょうか? ///////////////////////////////////////////////////////////////// 例えば、 ・ファイルにデータを出力する。(例えば、バッチファイルhoge.batとして) その後、すぐに ・そのバッチファイルを実行する命令を書く(Runtime.exec(hoge.bat); ///////////////////////////////////////////////////////////////// このプログラムを実行した場合、スレッドAがファイルにデータを出力し終わる前に勝手にスレッドBが作られ、Runtime.exec(hoge.bat);が実行されてしまいます。正しくスレッドAが終了し終わった後にスレッドBが実行し始めるようにするにはどうすればいいのでしょうか?教えてください。お願いします。

    • ベストアンサー
    • Java
  • リクエスト処理スレッド

    アプリケーションサーバーで Webアプリを動かし、 リクエスト処理スレッドが復帰しない場合、 つまり数分間ビジーな場合、エラーが出ることを確認しなければならなくなりました。 単にビジーになればいいのかと、 whileで終了しないループを作ってみたのですが、 それではリクエスト処理スレッドにはならなく、タイムアウトにはなったものの、目的のエラーが出てくれませんでした。 リクエスト処理スレッドがビジーとなるようなプログラムをどう組めばいいか教えてください。 ヒントだけでも助かります

    • ベストアンサー
    • Java
  • スレッド中でウインドウのイベント(Windows)

    カテゴリー違いかもしれませんが、 Windowsでスレッド中でウインドウを生成すると、 イベントの処理ができなくなります。 (たぶんもとのイベントループにメッセージがいって  そこで終わっていると思うのですが) これを避ける方法とかあるのでしょうか? メインループにメッセージを送ってそこでウインドウを 生成してもらうのが由緒正しい方法なのでしょうか? 用語とかむちゃくちゃかもしれませんが よろしくお願いします。

  • 2つのスレッドの実行について

    お世話になります。 VS2005C#で作成しております。 以下のコードにてスレッドを2つ実行させ、終了を監視する処理を記述しました。 ----------------------------------------------------------------------------------------------------------- isRcvSearchFlg = 0; isFwdSearchFlg = 0; private int SetDataTreeView() { // それぞれのデータ格納処理をスレッドで処理する Thread RcvSetThread = new Thread(new ThreadStart(this.RcvDataSet)); Thread FwdSetThread = new Thread(new ThreadStart(this.FwdDataSet));   RcvSetThread.Start(); FwdSetThread.Start(); // 両方のスレッドが終了するまで待機する while (isRcvSearchFlg != 1 && isFwdSearchFlg != 1) Application.DoEvents(); } delegate void RcvDataSetDelegate(); void RcvDataGridSet() { /* メインフォーム内のDatagidview(1)への値の代入 */ isRcvSearchFlg = 1; } void RcvDataSet() { Invoke(new RcvDataSetDelegate(RcvDataGridSet)); } delegate void FwdDataSetDelegate(); private void FwdDataGridSet() { /* メインフォーム内のDatagidview(2)への値の代入 */ isRcvSearchFlg = 1; } void FwdDataSet() { Invoke(new FwdDataSetDelegate(FwdDataGridSet)); } ----------------------------------------------------------------------------------------------------------- RcvDataGridSet、FwdDataGridSetでログを出力して、進行状況を監視しておりますが、どうも2つのスレッドが同時に走っていないようなのです。 原因として考えられることはございますでしょうか。 また、同時に走らせるコードはありますでしょうか。 お手数ですが、ご教授いただきたく宜しくお願い申し上げます。

専門家に質問してみよう