マルチスレッドの強制終了

[戻る]
にし 2002/04/11(木) 19:53:16
毎度お世話になります。

今、VC++6のMFCでマルチスレッドプログラム作成の準備として
簡単なモデルプログラム(下記)を作っています。
モデルプログラムは、ワーカースレッドのカウントUPをEditBoxに表示する簡単なもの
ですが、スレッド動作中にスレッドを強制終了(OnDestroy)させると
メモリリークが発生します。

Detected memory leaks!
Dumping objects ->
thrdcore.cpp(166) : {63} client block at 0x00374688, subtype 0, 112 bytes long.
a CWinThread object at $00374688, 112 bytes long
Object dump complete.

スレッド動作を一旦中断(OnThreadCan)させてから中止(OnDestroy) させれば
メモリリークは起きません。何か気付いた所がありましたらお願いします。

//----- モデルプログラムの一部

UINT ThreadProc(LPVOID pParam);
CEvent m_pTermThreadsEvent;

//----------スレッド開始-------------------------

void CThreadDlg::OnThread()
{
    AfxBeginThread(ThreadProc,GetSafeHwnd());    
}

//---------スレッド中断--------------------------

void CThreadDlg::OnThreadCan()
{
    m_pTermThreadsEvent.SetEvent();    //停止信号をThreadProcに送る
}

//---------スレッド------------------------------

UINT ThreadProc(LPVOID pParam)
{
    int sum = 0;

    for (int i = 0; i < 300; i++) {
    Sleep(20);        //動作遅延
    sum++;            //加算
            SetDlgItemInt((HWND)pParam, IDC_EDIT1, UINT(sum), TRUE); //加算表示
    //停止信号チェック
    if(::WaitForSingleObject(m_pTermThreadsEvent, 0) == WAIT_OBJECT_0) {  
        AfxEndThread(0);
        break;
    }
    }
    return 0;
}

//-----------スレッド中止-----------------------

void CThreadDlg::OnDestroy()
{
    CDialog::OnDestroy();
    
    m_pTermThreadsEvent.SetEvent();     //停止信号をThreadProcに送る
    WaitForSingleObject(m_pTermThreadsEvent, INFINITE); //スレッド終了待ち
}

TAD [E-Mail] 2002/04/12(金) 05:50:20
OnDestroy で、基本クラスを呼び出す前に、スレッドの終了待ちをしてみてはどうでしょう。
または、OnDestroy ではなく、OnClose を使ってみるのもいいかもしれません。その場合、
OnOK などにもスレッド終了処理を記述する必要がありますけど。

にし 2002/04/12(金) 09:13:54
TADさん 有難うございます。

>スレッドの終了待ちをしてみてはどうでしょう

OnDestroyのWaitForSingleObjectでスレッドの終了待ちをしているつもりなんですが
これでは不十分なんでしょうか。
OnCloseは試してみます。

dairygoods 2002/04/12(金) 09:51:42
> WaitForSingleObject(m_pTermThreadsEvent, INFINITE); //スレッド終了待ち

これは m_pTermThreadsEvent が SetEvent されるのを待つだけに見えますが、
このような方法が可能なのでしょうか?

スレッドの終了を待つときは、スレッドハンドルに対してWaitします。

Puppy 2002/04/12(金) 10:09:03
Puppyです。 dairygoodsさん おはようございます。

>スレッドの終了を待つときは、スレッドハンドルに対してWaitします。

そうしないと、いけないんでしょうか?

Puppyもワーカスレッド内でEventオブジェクトをWaitForSingleObjectで
待機しているんですけどぉ。何か問題が起きますか?

よろしくお願いします。m(..)mぺこり

TAD [E-Mail] 2002/04/12(金) 10:13:48
CEvent の基本クラス、CSyncObject の変換演算子によって、HANDLE 値が返されるようです。
コンパイル時にエラーや警告が起こっていないのなら、問題ないと思いますが、HANDLE 型にキャ
ストすれば確実でしょう。

Puppy 2002/04/12(金) 11:15:10
Puppyです。 TADさん お返事ありがとうございます。

にしさん 横から、入ってきてごめんなさい。

>コンパイル時にエラーや警告が起こっていないのなら、問題ないと思いますが、HANDLE 
>型にキャストすれば確実でしょう。

CreateEvent APIでイベントオブジェクトを作成してます。
そのイベントオブジェクトのHANDLEをWaitForSingleObjectで待ってます。

問題ないでしょうか?m(..)mぺこり

にし 2002/04/12(金) 11:29:57
dairygoodsさん、 Puppyさん、 TADさん 有難うございます。

>スレッドの終了を待つときは、スレッドハンドルに対してWaitします。 

確かに、WaitForSingleObjectのHelpをみると「オブジェクトのハンドルを指定します」
とかいてあるので、ハンドルの指定かと思ったのですが、AfxBeginThreadの戻り値は
ポインタなのと、各サイトのQ&Aを見るとこの方式が結構使われているようなので
使用したわけです。

>コンパイル時にエラーや警告が起こっていないのなら
 コンパイルではエラーは起きていません。

>HANDLE 型にキャストすれば確実でしょう。
 はなはだ申し訳ありませんが、何処をどうゆふうにCASTすればいいのか
 ヒントだけでもいただけないでしょうか?

TAD [E-Mail] 2002/04/12(金) 11:48:34
Puppyさん>

Puppy さんも、スレッドの終了処理ですか? それなら、ワーカースレッドで待機するのでは
なく、主スレッドで待機するべきではないでしょうか。主スレッドで、ワーカースレッドの終了を
待機、確認してから、主スレッドの終了処理を行います。
CreateEvent で作成したハンドルを指定しているは、間違っていませんよ。


にしさん>

キャストは普通のキャストと同じです。

WaitForSingleObject((HANDLE)m_pTermThreadsEvent, INFINITE);

とするだけです。

しかし、先ほども言いましたが、やらなくても問題はないはずです。
この位置にブレークポイントを設定して止めてから、ステップイン(F11)してみると、
operator HANDLE() が呼ばれているのが確認できると思います。


CEvent はイベントオブジェクトをカプセル化しただけのもので、コンストラクタで API の 
CreateEvent が呼ばれています。

Puppy 2002/04/12(金) 11:49:13
Puppyです。

WaitForSingleObject((HANDLE)m_pTermThreadsEvent, INFINITE); //スレッド終了待ち

こーゆーことでしょうか?
違ってたら、ごめんなさい。m(..)mぺこり

Puppy 2002/04/12(金) 11:54:43
Puppyです。 TADさん お返事ありがとうございます。

>Puppy さんも、スレッドの終了処理ですか? 

はい。

>それなら、ワーカースレッドで待機するのでは
>なく、主スレッドで待機するべきではないでしょうか。
>主スレッドで、ワーカースレッドの終了を
>待機、確認してから、主スレッドの終了処理を行います。

そうなんですか。よくわかっていないので。。。
どこかに例題ありませんか。
もうすこしよく勉強して、出直しますぅ。m(..)m

dairygoods 2002/04/12(金) 12:51:30
> 確かに、WaitForSingleObjectのHelpをみると「オブジェクトのハンドルを指定します」
> とかいてあるので、ハンドルの指定かと思ったのですが、AfxBeginThreadの戻り値は
> ポインタなのと、各サイトのQ&Aを見るとこの方式が結構使われているようなので
> 使用したわけです。

すみません、ハンドルかポインタかと言うことではなく、
最初のコードを見る限り、m_pTermThreadsEvent は、AfxBeginThread の
戻り値ではなく、単なる CEvent ですよね。

スレッドの終了を待つのであれば、

CWinThread* pThread = AfxBeginThread(...);
...
::WaitForSingleObject(*pThread, INFINITE);

としなければならないと思います。

TAD [E-Mail] 2002/04/12(金) 13:45:44
マルチスレッドを使った、過去の自分のソースを見てみました。
MFC ではなく API だったのですが、イベントオブジェクトを作ることなく、CreateThread で得
たスレッドハンドルを WaitForSingleObject に渡して終了待ちをしていました。
とすると、dairygoods さんの示してくれたコードでいけそうです。
dairygoods さん、ご指摘ありがとうございました。

TAD [E-Mail] 2002/04/12(金) 13:56:06
追加です。

>m_pTermThreadsEvent.SetEvent();     //停止信号をThreadProcに送る
>WaitForSingleObject(m_pTermThreadsEvent, INFINITE); //スレッド終了待ち

これだと確かにおかしいですね。

SetEvent でシグナル状態にしているのですから、次の WaitForSingleObject は無意味になっ
てしまいます。ここをスレッドのハンドルに変えれば正しく動作するはずです。

うーん、どうして最初に気が付かなかったんだろう。

Bun 2002/04/12(金) 14:51:55
>>m_pTermThreadsEvent.SetEvent();     //停止信号をThreadProcに送る
>>WaitForSingleObject(m_pTermThreadsEvent, INFINITE); //スレッド終了待ち
>これだと確かにおかしいですね。

おかしいでしょうか?

元発言を見ると、問題ないように思えますけど。

SetEventすることによって、スレッド終了のトリガをかけているのではないでしょうか?

INFINITE とすべきではないと思いますが。

いもちぃ 2002/04/12(金) 15:10:41
>スレッド動作を一旦中断(OnThreadCan)させてから中止(OnDestroy) させれば
>メモリリークは起きません。何か気付いた所がありましたらお願いします。

これ、たまたまスレッドが先に終わったんでしょう。
SetEventした直後にプログラムが終わったら、Threadが正常に終了する前に
プログラムが終了することになり、おかしいことになります。
もし各スレッドがちゃんとCPUリソースを割り振られていたなら、
SetEventした直後にWaitForSingleObject(m_pTermThreadsEvent〜
して(Eventは立ってるからすぐに処理が戻ってきます)すぐに
終了することになります。ところが、ひとつ関数をかますことによって、
関数を読んだりモタモタしてるうちに、SetEventを察したスレッドの
方が終わる猶予ができるわけですよね。

多分、
>m_pTermThreadsEvent.SetEvent();     //停止信号をThreadProcに送る
Sleep(3000);
>WaitForSingleObject(m_pTermThreadsEvent, INFINITE); //スレッド終了待ち
とか入れれば、リークは起きないと思います。

スレッドはかなりシビアなタイミングで動きますから、しっかりと
内容を把握していないとバグの原因をめっけられません。

正しいのは、dairygoodsさんやTADさんのおっしゃるようにスレッドハンドルを
WaitForSingleObjectすることです。

dairygoods 2002/04/12(金) 15:13:52
1> void CThreadDlg::OnDestroy()
2> {
3>     CDialog::OnDestroy();
4>     
5>     m_pTermThreadsEvent.SetEvent();     //停止信号をThreadProcに送る
6>     WaitForSingleObject(m_pTermThreadsEvent, INFINITE); //スレッド終了待ち
7> }

5行目でm_pTermThreadEventはシグナル状態になります。
6行目でm_pTermThreadEventがシグナル状態になるまで待っていますが、
直前でシグナル状態にセットしているので、関数はすぐに戻ります。

CEventはデフォルトではオートイベントであり、一度シグナル状態で
あることを検出されると、非シグナル状態に自動的に戻ります。
そのため、ThreadProc() 関数内の、

> //停止信号チェック
> if(::WaitForSingleObject(m_pTermThreadsEvent, 0) == WAIT_OBJECT_0) {  

の部分で、イベントがシグナル状態になっていることを検出するには、
この部分が偶然上記5行目と6行目の間で実行されたときだけです。
これは、滅多に起こりそうもありません。そのため、スレッドは終了しません。

偶然にもスレッド側でイベントがシグナル状態になったのを検出した場合は、
今度はメインスレッドの方が6行目で無限に待つことになります。

Bun 2002/04/12(金) 15:31:34
>CEventはデフォルトではオートイベントであり、一度シグナル状態で
>あることを検出されると、非シグナル状態に自動的に戻ります。

失礼しました。デフォルトはオートなんですね。
やはり、スレッドハンドル(ポインタ)で待機すべきですね。

あと、元発言のスレッドは時間がたてば終了するんですね。
(中断ってかいてあった (^^; )

にし 2002/04/12(金) 17:01:04
TADさん、 dairygoodsさん、 Bunさん、 いもちぃさん 有難うございます。

こんなに難しい事をやっていたとは思ってもいませんでした(汗)。

>CWinThread* pThread = AfxBeginThread(...);
>...
>::WaitForSingleObject(*pThread, INFINITE);

この*pThreadを、使っているWaitForSingleObjectのすべてを入れ替えたところ、
OnThreadCanが効かなくなりました。
>今度はメインスレッドの方が6行目で無限に待つことになります。
同時に、OnDestroyのWaitForSingleObject(m_pTermThreadsEvent, INFINITE);
で永久に待ちつずけるようになりました。SetEventの引数はないので他の所も変更
しないと駄目なのかも知れませんね。

>WaitForSingleObject((HANDLE)m_pTermThreadsEvent, INFINITE);

この(HANDLE)m_pTermThreadsEventをやはり使っている所すべてを入れ替えたところ
OnDestroy(右上のX印)させてもメモリリークはなくなりました。ただ、終了コードが
0x2になりましたが、メモリリークとは別物かと考えています。

>これ、たまたまスレッドが先に終わったんでしょう。

ThreadProcの最高カウント値300を5000に変更し、カウント値を100,200,300,400で
中断し、すぐさま(マウスの移動時間)終了(OnDestroy)しても、メモリリークは
起きませんでした。皆さんの考えによると、この方式はおかしいと言う事なので
たまたま上手くいってるだけでしょうかね。

Bun 2002/04/12(金) 17:11:51
>こんなに難しい事をやっていたとは思ってもいませんでした(汗)。

わしもでーす。(大&&滝汗)

>この*pThreadを、使っているWaitForSingleObjectのすべてを入れ替えたところ、
>OnThreadCanが効かなくなりました。

そうなりますね。中断が必要ならEventオブジェクトは必要だと思います。
APIを使ったサンプルですが、下記のURLを参考にしてみてください。
(ぱっとしか見てないけど、結構感じはつかめるかも)

http://www.geocities.co.jp/SiliconValley-PaloAlto/5920/api_sample_sync12_c.html

dairygoods 2002/04/12(金) 17:25:56
> この*pThreadを、使っているWaitForSingleObjectのすべてを入れ替えたところ、
> OnThreadCanが効かなくなりました。

全て置き換えてはいけませんよ(^^;

メインスレッド(OnDestroy)が待つのは、「スレッドの終了」で、
ワーカースレッド(ThreadProc)が待つのは、「イベントがSetEventされたかどうか」です。
待つものが違うのですから、2つのWaitForSingleObjectが同じではうまくいきません。

最初のコードで、スレッド終了待ちの方だけ *pThread を待つように
変更すれば、正しく動作すると思います。

ところで、pThreadは、スレッドが終了すると自動的にdeleteされてしまいますので、
開始した直後に
  pThread->m_bAutoDelete = FALSE;
としておいて、スレッドの終了を待った後、自分でdeleteする必要があります。

にし 2002/04/12(金) 19:12:17
[[解決]]
Bunさん、 dairygoodsさん 有難うございました。
TADさん、 いもちぃさん 有難うございました。

>http://www.geocities.co.jp/SiliconValley-PaloAlto/5920/api_sample_sync12_c.html
早速見て見ます。

>全て置き換えてはいけませんよ
すぐやってみます。

皆さんから、いろいろアドバイスを頂き有難うございました。
ラウンジを大変消費してしまいましたので、これでとりあえず「解決」に
したいと思います。

にし 2002/04/12(金) 19:51:25
[[解決]]
>全て置き換えてはいけませんよ
>すぐやってみます。

「解決」後ですが、上記、リークなしで動作しましたので追記します。

古いスレッドには追加発言できません。