ブロックしているWin32スレッドを停止するには?

私は _beginthreadex()でいくつかのwin32スレッドを開始するカスタム ThreadPool を作成しました。スレッドはブロッキングキューからタスクをデキューしようとする単純なループを実行していますが、スレッドを停止する必要がある場合があります。また、デキューでブロックされている場合は、そのブロッキング状態から抜け出す。

void ThreadPool::Loop()
{
    while(_running)
    {
        try
        {
           //Attempts to dequeue a task and run it
            _taskQueue.Dequeue()->Run();
        }
        catch(BlockingQueueTerminate&)
        {
           //Eat the exception and check the running flag
            continue;
        }
    }
}

私の考えは、プール内にスレッドがあるので、同じ数の特別なタスクをエンキューすることでした(「終了タスク」と呼ぶ)。各「終了タスク」は終了するために _endthreadex(0)を呼び出しますスレッド。ブロッキングキューに他のタスクがある場合は、タスクをデキューするとすぐに実行し、スレッドが必要かどうかを判断するために _running フラグをチェックしますので、これ以上タスクをデキューしないでください。

void TerminationTask::Run()
{
    _endthreadex(0);
}

私はこのアプローチについていくつか心配しています。主に、非終了タスクを処理し、 _running フラグが false に設定されていると、スレッドは _endthreadex(0)それはループを終了します。私はループの最後に _endthreadex(0)を次のように呼び出すことができるかどうか疑問に思っていました:

void ThreadPool::Loop()
{
    while(_running)
    {
        try
        {
           //Attempts to dequeue a task and run it
            _taskQueue.Dequeue()->Run();
        }
        catch(BlockingQueueTerminate&)
        {
           //Eat the exception and check the running flag
            continue;
        }
    }
    _endthreadex(0);
}

これは TerminationTask と競合しますか、または TerminationTask :: Run()の実行後にスレッドがループを直ちに終了します(つまり、 _endthreadex 0)を2回)?さらに、これよりも優れたアプローチがありますか?

5

2 答え

スレッドメソッドの最後に _endthreadex(0)を呼び出すのは問題ありません。またオプションです。スレッドメソッドを通常のままにしておくと、 _endthreadex(0)が呼び出されます。

_endthreadまたは_endthreadexを明示的に呼び出してスレッドを終了することができます。しかし_endthreadまたは_endthreadexは、スレッドがパラメータとして渡されたルーチンから_beginthreadまたは_beginthreadexに戻るときに自動的に呼び出されます。 ref

Sending a termination task is the correct way to get a blocked thread pool thread to unblock and quit.

だから、要約すると:

  1. あなたの戦略は良好で、 TerminationTask :: Run の実装は正しいです。
  2. ThreadPool :: Loop の最後に、 _endthreadex(0)の無害な呼び出しを削除することができます。
6
追加された
ああ、持っています...私は不要な _endthreadex(0)を削除します!確認していただいてありがとうございます!
追加された 著者 Kiril,
キューにたくさんのタスクがあり、すべてのタスクを処理する前に終了したい場合に備えて、 _running フラグが必要です。終了タスクは、キューにタスクがなく、ブロック状態から抜け出したい場合にのみ役立ちます。つまり、 ThreadPool :: Terminate (すべてのタスクをブロッキングキューの最後に TerminateTask を追加するだけで処理できます。キューに入っているものに関係なく単に処理を停止する ThreadPool :: TerminateNow メソッドを持つことができます。
追加された 著者 Kiril,
@ JohnDiblingあなたはそれを行う方法を説明できますか?あなたのコメントは、 Runnable タスクにいくつかのブロックコードがある場合を考えました...私はそれを制御していないので、 Runnable も?おそらく、私は Runnable のためのある種のポリシーを実装して、ブロック状態から取り除く "割り込み"メソッドを持たせるべきです(インタフェースを実装する人の責任となります)それ)。
追加された 著者 Kiril,
@David、何らかの理由でWindowsの ThreadPool が何らかの形で不十分であると判断したので、Java の動作を模倣する独自の ThreadPool > ThreadPool (私はJavaで動作する方法が好きです)。しかし、もう一度winapiを見てみると、winapi ThreadPool を使わない正当な理由が本当にあるかどうかを調べるつもりです。
追加された 著者 Kiril,
+1するが、別のカップルオプションは1) WaitForMultipleObjects でブロックされたスレッドをスリープ状態にするための "death event"を設定するか、 QueueUserAPC
追加された 著者 John Dibling,
さて、 while(_running)ループが必要ですか?私がこれを行ったときはいつでも while(true)を書いて終了手続きを送って手続きを終わらせました。終了メカニズムがあるので、 _running フラグに別のフラグを設定するのは偽りのようです。確かに、あなたはコードが不必要に複雑になり、競争条件の可能性を担うこのようにコードされていると思われます。
追加された 著者 David Heffernan,
はい、私は理解しています!
追加された 著者 David Heffernan,
非同期キャンセルを実装するのはかなり困難です。 .NETのようなフレームワークは、そのレベルの機能性を提供します。別の考えでは、標準のWindowsスレッドプールでは必要なことはしません。結局のところ、あなたはwinapiという質問にタグをつけました。
追加された 著者 David Heffernan,

待ち行列にteminationタスクを置くことは正しいです。私はそれを扱うために別のアプローチを試みるだろう:

class TerminateThreadNow {};

void TerminationTask::Run()
{
    throw TerminateThreadNow();
} 

void ThreadPool::Loop()
{
    while(_running)
    {
        try
        {
           //Attempts to dequeue a task and run it
            _taskQueue.Dequeue()->Run();
        }
        catch(TerminateThreadNow&)
        {
            _running = false;
        }
        catch(BlockingQueueTerminate&)
        {
           //Eat the exception and check the running flag
        }
    }
} 
0
追加された