none
スレッドの終了を待機するWaitForSingleObjectで、スレッドの終端まで実行されたにも関わらずWAIT_OBJECT_0が返らない事がある

    質問

  • お世話になっております。

    WaitForSingleObjectでタイムアウト有(30秒)でスレッドの終了を待機しています。
    この戻り値が、稀(0.1%以下)にWAIT_OBJECT_0以外の事があります。
    スレッド内部の終端にログ出力を仕込んでおり、そのログの出力時刻から30秒後にWaitForSingleObjectを抜けています。
    このため、タイムアウト発生(WAIT_TIMEOUT)を推測しています。
    スレッドがreturnまで処理を進めたにも関わらず、その後のイベント通知に時間がかかる、もしくは失われるケースは有るのでしょうか?

    Visual C++ のランタイムは、"Microsoft Visual C++ 2015 Redistributable (x86) - 14.0.23026"を使用しています。
    ソースコードはC言語で記述されており、以下の記述でスレッドの終了を待機しています。

    // hThreadはスレッドのハンドルを含む構造体へのポインタ
    if (OS_WaitThread(hThread, 30 * 1000, nIndex)) != WAIT_OBJECT_0) {
    	OS_TerminateThread(COMSTRUCT(hWin32Com)->hRecThread, 201, nIndex);
    	bRet = FALSE;
    }
    

    ここで、"OS_WaitThread"は内部でWaitForSingleObjectを、"OS_TerminateThread"は内部で"TerminateThread"をCALLしています。

    DWORD OS_WaitThread(THREADHANDLE hThread, DWORD nMSecTime, int com_index)
    {
    	DWORD	dwResult;
    
    	dwResult = WaitForSingleObject(((HTHREADINFO)(hThread->m_Handle))->m_Handle,nMSecTime);
    	return(dwResult);
    }
    
    BOOL OS_TerminateThread(THREADHANDLE hThread, DWORD dwResult, int com_index)
    {
    	BOOL	bRet;
    
    	bRet = TerminateThread( ((HTHREADINFO)(hThread->m_Handle))->m_Handle, dwResult );
    	return( bRet );
    }
    

    ご教示頂けますと幸いです。
    宜しくお願い致します。


    2017年3月27日 4:02

回答

  • Windows は RTOS (Real-Time OS) ではないので、完全な同期処理は保証されていないはずです。
    このため、提示されているコードを実行した場合、数クロックの遅延が発生する可能性は常にあり得ると思います。

    具体的には。。。。
    WaitForSingleObject() API での待機処理は、ソフトウェア割り込みにより実現されていると考えられますが、Windows アーキテクチャにおけるソフトウェア割り込みは、常に最優先で処理される訳ではありません。
    ソフトウェア割り込みよりも高い IRQL (Interrupt ReQuest Levels) での処理が存在しない場合にのみ、そのソフトウェア割り込みが最優先で処理されるはずです。
    例えば、ソフトウェア割り込みと同じタイミングでハードウェア割り込みが発生した場合、ハードウェア割り込みの方が IQRL の高いリクエストになるので、ソフトウェア割り込みのリクエストは後回しにされ、結果的に遅延するはずです。
    ここら辺の仕様については、下記サイトに詳細な解説がありますので、そちらを参照されることをお勧めします。

    ---------------------------------------------------------------
    Processes, Threads, and Jobs in the Windows Operating System
    https://www.microsoftpressstore.com/articles/article.aspx?p=2233328

    Interrupt Levels vs. Priority Levels
    ---------------------------------------------------------------

    上記サイトでの説明を読めばご理解いただけると思いますが、Windows アーキテクチャではその瞬間での IRQL が一番高いリクエストが CPU リソースを占有できる仕様となっています。
    この IRQL は 0 ~ 31 までの 32 段階ありますが、アプリケーション等のユーザー モード プログラムが利用できる IRQL は 0 (Passive Level) ~ 1 (APC Level) までの、最も低い IRQL です。
    ソフトウェア割り込みですら 1 (APC Level) ~ 2 (DPC/Dispatch Level) までであり、ハードウェア割り込みよりも優先して実行させることは、事実上不可能です。
    つまり、キーボードやマウスあるいはそれ以外の何かのハードウェア割り込みにより、ソフトウェア割り込み処理が中断あるいは後回しにされる可能性は潜在的に常にある訳で、ご質問されている現象もこれに該当しているんだと思います。

    追記
    スレッド ハンドルの実体 (のスレッド) が終了してしまった場合でも、ハンドル自体は有効なはずです。
    またそのスレッドに関連付けられた全てのハンドルがすべてクローズされない限り、そのスレッドは削除されないはずだったと思います。
    つまりスレッド ハンドルをリークさせてしまうと、そのスレッドがゾンビ化するはず。
    ただし、スレッド ハンドルをリークさせたプロセスが消去されれば、ハンドル リークが解消されるので、ゾンビ化したスレッドも成仏する。。。という動きだったような。。。


    2017年3月27日 8:35

すべての返信

  • ニュアルによると、WaitForSingleObject()は、

    1.失敗した場合、WAIT_FAILED が戻る

    2.成功した場合は以下のいずれかが戻る
     WAIT_ABANDONED
     WAIT_OBJECT_0
     WAIT_TIMEOUT

    とあります。
    30[s]以内にスレッドがシグナル状態にならなければ、関数OS_WaitThread()内の、
    WaitForSingleObject()に行ったきりになり、return;には到達しません。
    30[s]後、WAIT_TIMEOUTが関数から戻ると予測できます。
    で、どのような疑問をお持ちでしょうか。

    2017年3月27日 5:21
  • 返信ありがとうございます。

    ワーカースレッド内部の終端にログ出力を仕込んでいます。
    そのログ出力がある事から、ワーカースレッドは処理の終端(return)まで到達していると推察しています。
    従ってログからは、ワーカースレッドが終端に達した後に、タイムアウト時間(30秒)を経過して漸くメインスレッドのWaitForSingleObjectが処理を返すように見えます。
    お尋ねしたいのは、「スレッド終了後、そのイベント通知に時間がかかる・もしくは失われるケースがあるのか否か」になります。
    2017年3月27日 5:49
  • >「スレッド終了後、そのイベント通知に時間がかかる・もしくは失われるケースがあるのか否か」
    一般的にはないと思います。

    ログによって、そのスレッドの完了が確認できているとのことですので、
    そのつもりはないのに、既に終了してしまったスレッドのハンドルを
    WaitForSingleObject()に渡してしまった。
    というケースなどを、疑ってみるのはどうでしょう。

    例えば、次のような手順の場合は、その可能性があります。

     1.(サスペンドでなく)子スレッドを起動。
     2.WaitForSingleObject()で子スレッドの終了待ち。

    という手順の場合、1.と2.の間で

     1.5 あっという間に子スレッドが処理完了。

    してしまうと、まずいかもしれません。

    2017年3月27日 7:25
  • Windows は RTOS (Real-Time OS) ではないので、完全な同期処理は保証されていないはずです。
    このため、提示されているコードを実行した場合、数クロックの遅延が発生する可能性は常にあり得ると思います。

    具体的には。。。。
    WaitForSingleObject() API での待機処理は、ソフトウェア割り込みにより実現されていると考えられますが、Windows アーキテクチャにおけるソフトウェア割り込みは、常に最優先で処理される訳ではありません。
    ソフトウェア割り込みよりも高い IRQL (Interrupt ReQuest Levels) での処理が存在しない場合にのみ、そのソフトウェア割り込みが最優先で処理されるはずです。
    例えば、ソフトウェア割り込みと同じタイミングでハードウェア割り込みが発生した場合、ハードウェア割り込みの方が IQRL の高いリクエストになるので、ソフトウェア割り込みのリクエストは後回しにされ、結果的に遅延するはずです。
    ここら辺の仕様については、下記サイトに詳細な解説がありますので、そちらを参照されることをお勧めします。

    ---------------------------------------------------------------
    Processes, Threads, and Jobs in the Windows Operating System
    https://www.microsoftpressstore.com/articles/article.aspx?p=2233328

    Interrupt Levels vs. Priority Levels
    ---------------------------------------------------------------

    上記サイトでの説明を読めばご理解いただけると思いますが、Windows アーキテクチャではその瞬間での IRQL が一番高いリクエストが CPU リソースを占有できる仕様となっています。
    この IRQL は 0 ~ 31 までの 32 段階ありますが、アプリケーション等のユーザー モード プログラムが利用できる IRQL は 0 (Passive Level) ~ 1 (APC Level) までの、最も低い IRQL です。
    ソフトウェア割り込みですら 1 (APC Level) ~ 2 (DPC/Dispatch Level) までであり、ハードウェア割り込みよりも優先して実行させることは、事実上不可能です。
    つまり、キーボードやマウスあるいはそれ以外の何かのハードウェア割り込みにより、ソフトウェア割り込み処理が中断あるいは後回しにされる可能性は潜在的に常にある訳で、ご質問されている現象もこれに該当しているんだと思います。

    追記
    スレッド ハンドルの実体 (のスレッド) が終了してしまった場合でも、ハンドル自体は有効なはずです。
    またそのスレッドに関連付けられた全てのハンドルがすべてクローズされない限り、そのスレッドは削除されないはずだったと思います。
    つまりスレッド ハンドルをリークさせてしまうと、そのスレッドがゾンビ化するはず。
    ただし、スレッド ハンドルをリークさせたプロセスが消去されれば、ハンドル リークが解消されるので、ゾンビ化したスレッドも成仏する。。。という動きだったような。。。


    2017年3月27日 8:35
  • 気のせいかもしれませんが、

    >  if (OS_WaitThread(hThread, 30 * 1000, nIndex)) != WAIT_OBJECT_0) {
    >      OS_TerminateThread(COMSTRUCT(hWin32Com)->hRecThread, 201, nIndex);

    完了を待つ対象スレッドと強制終了させるスレッドが異なるのはロジックとして正しいのでしょうか? 一般的には両者は同一スレッドを対象にすべきかなと思うのですが…。

    また質問文には「C言語」と書かれていますが、C++言語ということはないでしょうか? 両者は似て非なるもので、特にC++言語にはソースコード上に現れにくいデストラクターの処理が書けます。意図しない処理が走っていたりはしないでしょうか。


    2017年3月27日 9:17
  • 立花楓 さま
    私の返信は、「仲澤@失業者」さんの回答とは「真逆」の内容です。
    (私は自身の知識の範囲で、その理由を明記したつもりです。)
    従って、私の返信と「仲澤@失業者」さんの回答が一緒に「回答としてマーク」されるのは、矛盾しています。
    後から参照される方が混乱されるかもしれないので、このような状態は良くないと思います。
    「仲澤@失業者」さんの回答が「回答としてマーク」されるであれば、私の返信のマークを外してください。
    (確か以前にもこれと同じようなことがあったような。。。)


    追記
    ご対応いただき、ありがとうございました。
    • 編集済み お馬鹿 2017年4月5日 8:28 追記
    2017年4月4日 9:05
  • お馬鹿 さん、こんにちは
    フォーラム オペレーターの立花楓です。

    貴重なご意見をありがとうございます。

    本来「回答としてマーク」は質問者の課題が解決に向かった投稿に対して、質問者から付けていただくものとなっていますが、今回は回答の候補が多く回答マークの選定が難しいと判断したため、参考となりそうな回答に対してこちらで回答マークを付けさせていただきました。

    本来の方針に則り、質問者から回答マークをしていただくため、こちらで回答マークとさせていただいた投稿から回答マークを解除させていただきます。

    よろしくお願いいたします。


    MSDN/TechNet Community Support 立花楓

    2017年4月5日 7:55
    モデレータ
  • こんにちは、T.Toshi さん
    フォーラム オペレーターの立花楓です。

    仲澤@失業者 さん、お馬鹿 さん、佐祐理 さんから寄せられた情報はご確認いただけましたでしょうか。
    進展がございましたらこちらのスレッドへご返信いただけますと幸いです。

    また、参考になる情報がありましたら、投稿者からの [回答としてマーク] をお願いします。

    よろしくお願いいたします。


    MSDN/TechNet Community Support 立花楓

    2017年4月5日 8:04
    モデレータ
  • 回答が遅くなり申し訳ありません。

    既に終了したスレッド(ただしハンドルは削除しない)に対してWaitForSingleObjectを試行したところ、即座に処理が戻ってきました(dwMillisecondsパラメータを0にした場合も同様です)。
    この為、ご指摘の現象とは異なると考えています。

    2017年4月10日 0:50
  • 回答が遅くなり申し訳ありません。

    スレッドのハンドルとして、両者のハンドルは同一の変数を使用しています。
    見にくいコードで申し訳ありません…。

    また、記述言語ですが、C言語で間違いありません。
    大分以前にCで記述されたライブラリの修正を行っています。
    クラス構造や例外処理の有難みを痛感しています。

    2017年4月10日 0:58
  • 回答が遅くなり申し訳ありません。

    より優先度の高い割り込みソースを検討したのですが、ハードウェア割り込みを起こしそうなものはマウスやキーボード、COMポートくらいで特に怪しいものが見当たりません。
    それ以外の割り込みであっても、3分も占有するのであれば他のアプリケーションにも影響が出るのではと思っています。
    動作の雰囲気としてはスレッド終了でデッドロックが発生しているような感触です。

    当現象ですが、その後、その後全く再現しなくなってしまいました。
    この為、原因解明には至っていませんが、一旦CLOSEとさせていただき、割り込みの視点を与えていただいたこちらの回答をマークさせていただきます。

    2017年4月10日 1:11