スレッドを安全かつ確実に停止するには?

解決


どらちん  2011-01-25 08:34:27  No: 72234

いつもお世話になっております。

Win7 Enterprise(x64) + VS2010 Professional のVC++でSDKで開発をしてお
ります。

現在マルチスレッドアプリケーションを作成しているのですが、スレッドの
停止のさせ方について教えていただきたく書き込みをいたしました。

かなり端折りますが…

//停止用フラグをグローバル変数で宣言
BOOL g_fStop = FALSE;

//スレッドのハンドルとID
HANDLE g_hThread;
DWORD g_dwThreadID;

//スレッド関数
DWORD ThreadProc(void *param)
{
    for(;;)
    {
        if(g_fStop == TRUE)
        {
            break;
        }
        Sleep(1000);
    }
    return 0;
}

たとえばこのようなグローバル変数およびスレッド関数を作成したとします。
CreateThredでこのスレッドを開始後、停止をするときに、

DWORD l_dwExitCode;

g_fStop = TRUE;
do
{
    Sleep(500);
    GetExitCodeThread(g_hThread, &l_dwExitCode);
}while(l_dwExitCode == STILL_ACTIVE);

とすれば、停止まで待機して安全に終了してくれると思ったのですが…
いつまでたっても、STILL_ACTIVEのままなんです。

デバッグでブレークポイントにThreadProcの
        if(g_fStop == TRUE)
        {
            break;
        }
のbreakの行に入れてみたのですが、グローバル変数を変更しても、ここに到
達しないんです。

TerminateThreadを実行してしまうと、スレッド関数内で利用しているポイン
タなどが正常に解放されずメモリリークの原因になると思うので、行いたく
ないのですが、このやり方は、誤っているのでしょうか?
(誤っているから停止しないのだと思うのですが…)

このようにスレッド内で無限ループになっているものを安全かつ確実に停止
するには、どのようにすればよいのでしょうか?

ご存知の方から見ればどうしようもない質問かもしれませんが…
なにとぞ、よろしくお願いいたします。


tetrapod  2011-01-25 15:51:54  No: 72235

volatile ぢゃないの?


瀬戸っぷ  2011-01-25 18:03:44  No: 72236

既に回答ついていますが……

>    for(;;)
>    {
>        if(g_fStop == TRUE)
>        {
>            break;
>        }
>        Sleep(1000);
>    }

だと、forループ中にg_fStopが変化するコードがありません。
であれば、
>    for(;;)
>    {
>        Sleep(1000);
>    }
と同じ意味になります。(変化がないのですから無駄なコードは省くように『最適化』されます)
他のスレッドで変更していようが、局所的にみるとそんな事実はコンパイラは知りません。
# 割り込み処理により特定レジスタのビットが変化する…とか、そういう場合でもコンパイラにとっては知ったことではありません。
が、それでは困るので最適化などでコードが削除されないようにvolatile指定が必要になります。
「volatile c言語」辺りで検索すると説明されているページも見つかるでしょう。

# ちなみに、私の場合スレッドの終了要求はイベントオブジェクトでやっています。
# 中断できるようなところでWaitForSingleObject()/WaitForMultipleObject()でチェックしています。
# 方法自体は何通りもある…でしょうけど。


仲澤@失業者  2011-01-25 19:11:15  No: 72237

tetrapodさんの発言をやや補足すると、
本件のように、別々のスレッドで相互に参照するデータは
volatile 宣言されていなければなりません。

コンパイラはvolatileでない変数は、同一スレッド内の
大域的に変化しないものとして、キャッシュやレジスタに
一時保管した変数と同一視して最適化してしまいます。
ある変数を「必ず当該のアドレスからロードしなおす」ためには、
つまり、キャッシュされないその時の実データを参照するには
volatile宣言されている必要があります。本件の場合

//停止用フラグをグローバル変数で宣言
volatile BOOL g_fStop = FALSE;

とすべきですね。

さて、スレッドの状態変化を感知する方法として、
より一般的にはイベントを使います。
この方法は瀬戸っぷさんの発言にある通りです。
ややコードが複雑化しますが、一般的である分
他人にはわかりやすいかもしれません。


どらちん  2011-01-26 00:23:32  No: 72238

tetrapodさん、瀬戸っぷさん、中澤@失業者さん
レスありがとうございます。

恥ずかしながら、volatile…知りませんでした^^;

イベントを使うことも考えたのですが、スレッド内のループで極力停止時間
をなくしたかったので、ループ内にWaitForSingleObjectが入れられないな〜
ということもあり、今回のようにした次第です。

ちなみに、いただいた情報をもとにいろいろ調べてみたのですが、PeekMessage
関数とPostThreadMessage関数を使って、ループ停止用のメッセージを送り、
スレッドを終了させる、という方法もありなんでしょうか?


仲澤@失業者  2011-01-26 02:29:21  No: 72239

>関数とPostThreadMessage関数を使って、ループ停止用のメッセージを送り、
>スレッドを終了させる、という方法もありなんでしょうか?

「あり」ですが、受信側のスレッドにメッセージキュー、又は
メッセージフックを作成しなければならない手間と、
本質的にはイベントよりも低速であることが想像できることを
勘案すると、さほど優秀な方法とは、思えません。どうでしょうか。
この方法は、自分もあんまり見たことのないコードです。


どらちん  2011-01-26 03:33:25  No: 72240

大ボケをしていましたが、WaitForSingleObjectのタイムアウト値って、0に
できるんですね^^;

これならスレッドのループ内の待機時間はほとんど変わらないと思うので、
イベントでスレッドの停止を実装してみます。

コード修正後、再度ご報告させていただこうと思います★


どらちん  2011-01-26 07:36:40  No: 72241

tetrapodさん、瀬戸っぷさん、中澤@失業者さん

イベントを利用したスレッドの停止でほぼ、うまくいきました。
ただ、同じプロセス内で、再度同じスレッドを呼び出した際に、終了のイベ
ントを受け取ってもらえず止まってしまう現象が出ていますが…

さらに質問なのですが、SetEventで終了のイベントをシグナルにしたとき、
スレッドの終了を待機する際に

1.WaitForSingleObjectを使う
WaitForSingleObject(hThread, INFINITE);

2.GetExitCodeThreadでループ
do
{
    Sleep(timeout);
    GetExitCodeThread(structThreadMgr->m_hThread, &l_dwExitCode);
}while(l_dwExitCode == STILL_ACTIVE);

などあると思いますが、どれも大して変わらないんでしょうか?
特に終了コードを求めていない場合は、1のほうが1行で済むので楽なよう
な気がしているのですが…

また、このような場合にスレッドの終了を永遠に待ち続けるようにすると、
停止に失敗してしまった場合に、アプリケーションの応答がなくなってしま
うと思うのですが、一般的にはタイムアウトを持たせるものなのでしょうか?


どらちん  2011-01-26 23:04:13  No: 72242

> ただ、同じプロセス内で、再度同じスレッドを呼び出した際に、終了のイベ
> ントを受け取ってもらえず止まってしまう現象が出ていますが…

これ、原因がわかりました。
どうやらスレッド停止後に、停止用のイベントをResetEventしただけだとこ
うなるみたいで、都度CloseHandleして、CreateThread直前にCreateHandle
で、再度イベントのハンドルを作り直すと、イベントをしっかりと受け取って
くれるようになりました。

当初投げていた質問に関してはこれで解決したと思いますので、チェックを
入れさせていただきました。

tetrapodさん、瀬戸っぷさん、中澤@失業者さん
いつもためになるアドバイス、ありがとうございます^^


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加