web-dev-qa-db-ja.com

長いスリープスレッドの停止

いくつかのタスクを定期的に実行するスレッドがあるとしますが、この期間は 1時間に6回 1時間に12回(5分ごと)、次のように、ループごとにチェックされるis_runningフラグでスレッドループを制御するコードをよく目にします。

_std::atomic<bool> is_running;

void start()
{
    is_running.store(true);
    std::thread { thread_function }.detach();
}

void stop()
{
    is_running.store(false);
}

void thread_function()
{
    using namespace std::literals;
    while (is_running.load())
    {
        // do some task...
        std::this_thread::sleep_for(5min);
    }
}
_

しかし、stop()関数が呼び出された場合、たとえば、start()から1ミリ秒後、スレッドは目覚まし、フラグをチェックして終了するまで、さらに299999ミリ秒間存続します。

私の理解は正しいですか?終了されるべきだったスレッドを存続させる(ただし、スリープさせる)ことを避ける方法は?これまでの私の最善のアプローチは次のとおりです。

_void thread_function()
{
    using namespace std::literals;
    while (is_running.load())
    {
        // do some task...
        for (unsigned int b = 0u, e = 1500u; is_running.load() && (b != e); ++b)
        {
            // 1500 * 200 = 300000ms = 5min
            std::this_thread::sleep_for(200ms);
        }
    }
}
_

これを達成するための汚れが少なく簡単な方法はありますか?

24
PaperBirdMaster

条件変数を使用します。条件変数またはが5分経過するのを待ちます。偽のウェイクアップを確認することを忘れないでください。

cppreference

Google検索の1〜2分で条件変数を使用する方法について、適切なスタックオーバーフローの投稿が見つかりません。トリッキーな部分は、waitが5分経過しても信号が送信されていなくても起動できることを認識していることです。これを処理する最もクリーンな方法は、ウェイクアップが「適切な」ものであることを再確認するラムダで待機メソッドを使用することです。

here は、cppreferenceでの_wait_until_をラムダとともに使用するサンプルコードです。 (ラムダ付きの_wait_for_は、ラムダ付きの_wait_until_と同等です)。少し修正しました。

ここにバージョンがあります:

_struct timer_killer {
  // returns false if killed:
  template<class R, class P>
  bool wait_for( std::chrono::duration<R,P> const& time ) const {
    std::unique_lock<std::mutex> lock(m);
    return !cv.wait_for(lock, time, [&]{return terminate;});
  }
  void kill() {
    std::unique_lock<std::mutex> lock(m);
    terminate=true; // should be modified inside mutex lock
    cv.notify_all(); // it is safe, and *sometimes* optimal, to do this outside the lock
  }
  // I like to explicitly delete/default special member functions:
  timer_killer() = default;
  timer_killer(timer_killer&&)=delete;
  timer_killer(timer_killer const&)=delete;
  timer_killer& operator=(timer_killer&&)=delete;
  timer_killer& operator=(timer_killer const&)=delete;
private:
  mutable std::condition_variable cv;
  mutable std::mutex m;
  bool terminate = false;
};
_

実例

共有スポットに_timer_killer_を作成します。クライアントスレッドはwait_for( time )を実行できます。 falseが返された場合は、待機が完了する前に殺された場所を意味します。

制御スレッドはkill()を呼び出すだけで、_wait_for_を実行するすべてのユーザーがfalseを返します。

いくつかの競合(ミューテックスのロック)があるため、これは無限スレッドには適していません(ただし、いくつかはそうではありません)。遅延繰り返しタスクごとに全スレッドではなく、任意の遅延で実行される無制限の数のタスクが必要な場合は、スケジューラの使用を検討してください。各実際のスレッドは、使用されているシステムアドレス空間のメガバイト以上です(スタックのみ)。 。

これを行うには、2つの従来の方法があります。

条件変数で時限待機を使用して、他のスレッドに定期的なスレッドに信号を送り、時間になるとウェイクアップして終了するようにすることができます。

代わりに、パイプでpollをスリープする代わりにタイムアウトとしてスリープすることもできます。次に、パイプにバイトを書き込むだけで、スレッドが起動し、終了できます。

2
Mark B