web-dev-qa-db-ja.com

スリープ中にstd :: threadを起こす方法

私はC++ 11を使用しており、クラスメンバーであるstd::threadがあり、2分ごとにリスナーに情報を送信します。それはただ眠るということ。そこで、2分間スリープさせてから、必要な情報を送信してから、再び2分間スリープさせました。

// MyClass.hpp
class MyClass {

    ~MyClass();
    RunMyThread();

private:
    std::thread my_thread;
    std::atomic<bool> m_running;
}


MyClass::RunMyThread() {

    my_thread = std::thread { [this, m_running] {
    m_running = true;
    while(m_running) {
        std::this_thread::sleep_for(std::chrono::minutes(2));
        SendStatusInfo(some_info);
    }
}};
}

// Destructor
~MyClass::MyClass() {
    m_running = false; // this wont work as the thread is sleeping. How to exit thread here?
}

問題:
このアプローチの問題は、スリープ中にスレッドを終了できないことです。 std::condition_variableを使用して起動し、正常に終了できることを読んで理解していますか?しかし、私は単純な例を見つけるのに苦労しています。これは上記のシナリオで必要とされる最低限のことを行います。私が見つけたcondition_variableの例はすべて、ここでやろうとしていることに対しては複雑すぎるように見えます。

質問:
std::condition_variableを使用してスレッドをスリープ解除し、スリープ中に正常に終了するにはどうすればよいですか?または、condition_variableテクニックなしで同じことを達成する他の方法はありますか?

さらに、std::mutexstd::condition_variableと組み合わせて使用​​する必要があることがわかりました。それは本当に必要ですか?ここでコードの必要な場所にのみstd::condition_variableロジックを追加して目標を達成することはできませんか?

環境:
コンパイラーgccおよびclangを使用したLinuxおよびUnix。

14
Game_Of_Threads

std::condition_variableを使用した実用的な例:

struct MyClass {
    MyClass()
        : my_thread([this]() { this->thread(); })
    {}

    ~MyClass() {
        {
            std::lock_guard<std::mutex> l(m_);
            stop_ = true;
        }
        c_.notify_one();
        my_thread.join();
    }

    void thread() {
        while(this->wait_for(std::chrono::minutes(2)))
            SendStatusInfo(some_info);
    }

    // Returns false if stop_ == true.
    template<class Duration>
    bool wait_for(Duration duration) {
        std::unique_lock<std::mutex> l(m_);
        return !c_.wait_for(l, duration, [this]() { return stop_; });
    }

    std::condition_variable c_;
    std::mutex m_;
    bool stop_ = false;
    std::thread my_thread;
};
7

std::condition_variableを使用してスレッドをスリープ解除し、スリープ中に正常に終了するにはどうすればよいですか?または、condition_variableテクニックなしで同じことを達成する他の方法はありますか?

いいえ、C++ 17の時点では標準C++ではありません(もちろん、非標準のプラットフォーム固有の方法があり、C++ 2aに何らかの種類のセマフォが追加される可能性があります)。

さらに、std::mutexstd::condition_variableと組み合わせて使用​​する必要があることがわかりました。それは本当に必要ですか?

はい。

ここでコード部分の必要な場所にのみstd::condition_variableロジックを追加して目標を達成することはできませんか?

いいえ。まず、ミューテックスをロックせずに(そしてロックオブジェクトをwait関数に渡すことなく)condition_variableで待機することはできません。したがって、ミューテックスが存在する必要があります。とにかくミューテックスを持たなければならないので、そのミューテックスを使用するためにウェイターと通知者の両方を必要とすることはそれほど大したことではありません。

条件変数は「偽のウェイクアップ」の対象となります。つまり、理由なしで待機を停止できることを意味します。通知されたために起動したのか、または誤って起動したのかを判断するには、通知スレッドによって設定され、待機スレッドによって読み取られる状態変数が必要です。その変数は複数のスレッドで共有されるため、安全にアクセスする必要があります。これはミューテックスによって保証されます。

共有変数にアトミック変数を使用する場合でも、通常、通知の欠落を防ぐためにミューテックスが必要です。

これは https://github.com/isocpp/CppCoreGuidelines/issues/554 で詳細に説明されています

15
Jonathan Wakely

Std :: condition_variableを使用してスレッドをスリープ解除し、スリープ中に正常に終了するにはどうすればよいですか?

std::condition_variable::wait_for()の代わりに std::this_thread::sleep_for() を使用すると、最初の割り込みは std::condition_variable::notify_one() または std::condition_variable::notify_all()

さらに、std :: condition_variableと共にstd :: mutexを使用する必要があることがわかりました。それは本当に必要ですか?ここでコード部分の必要な場所にのみstd :: condition_variableロジックを追加して目標を達成することはできませんか?

はい、std::mutexstd::condition_variableとともに使用する必要があり、フラグstd::atomicを作成する代わりに使用する必要があります。ここでミューテックスを使用します。

8
Slava

悲しいですが、本当の事実があります-あなたが探しているのはシグナルであり、Posixスレッドには真のシグナル伝達メカニズムがありません。

また、あらゆる種類のタイミングに関連付けられている唯一のPosixスレッドプリミティブは条件変数です。これが、オンライン検索がそれに導く理由です。C++スレッドモデルはPosix APIに基づいて構築されているため、標準C++ Posix互換プリミティブ取得する。

Posixの外に出たいと思わない限り(プラットフォームを指定しませんが、Linuxのeventfdなどの制限のないイベントを処理するネイティブプラットフォームの方法があります)、条件に固執する必要があります。変数とはい、条件変数を使用するにはAPIに組み込まれているため、ミューテックスが必要です。

あなたの質問は特にコードサンプルを要求するものではないので、私は何も提供していません。欲しい人がいれば教えてください。

5
SergeyA

さらに、std :: condition_variableと共にstd :: mutexを使用する必要があることがわかりました。それは本当に必要ですか?ここでコード部分の必要な場所にのみstd :: condition_variableロジックを追加して目標を達成することはできませんか?

_std::condition_variable_は低レベルのプリミティブです。実際に使用するには、他の低レベルのプリミティブもいじる必要があります。

_struct timed_waiter {
  void interrupt() {
    auto l = lock();
    interrupted = true;
    cv.notify_all();
  }
  // returns false if interrupted
  template<class Rep, class Period>
  bool wait_for( std::chrono::duration<Rep, Period> how_long ) const {
    auto l = lock();
    return !cv.wait_until( l,
      std::chrono::steady_clock::now() + how_long,
      [&]{
        return !interrupted;
      }
    );
  }
private:
  std::unique_lock<std::mutex> lock() const {
    return std::unique_lock<std::mutex>(m);
  }
  mutable std::mutex m;
  mutable std::condition_variable cv;
  bool interrupted = false;
};
_

待機したいスレッドと中断したいコードの両方が見える_timed_waiter_を作成するだけです。

待機中のスレッドは

_while(m_timer.wait_for(std::chrono::minutes(2))) {
    SendStatusInfo(some_info);
}
_

中断するには、m_timer.interrupt()(dtor内など)を実行してから、my_thread.join()を実行して終了します。

実例

_struct MyClass {
    ~MyClass();
    void RunMyThread();
private:
    std::thread my_thread;
    timed_waiter m_timer;
};


void MyClass::RunMyThread() {

    my_thread = std::thread {
      [this] {
      while(m_timer.wait_for(std::chrono::seconds(2))) {
        std::cout << "SendStatusInfo(some_info)\n";
      }
    }};
}

// Destructor
MyClass::~MyClass() {
    std::cout << "~MyClass::MyClass\n";
    m_timer.interrupt();
    my_thread.join();
    std::cout << "~MyClass::MyClass done\n";
}

int main() {
    std::cout << "start of main\n";
    {
        MyClass x;
        x.RunMyThread();
        using namespace std::literals;
        std::this_thread::sleep_for(11s);
    }
    std::cout << "end of main\n";
}
_

または、condition_variableテクニックなしで同じことを達成する他の方法はありますか?

std :: promise / std :: future を使用できますこの場合のbool/condition_variable/mutexのより単純な代替。 futureは偽のウェイクの影響を受けにくく、同期のためにmutexを必要としません。

基本的な例:

std::promise<void> pr;
std::thread thr{[fut = pr.get_future()]{
    while(true)
    {
        if(fut.wait_for(2min) != future_status::timeout)
            return;
    }
}};
//When ready to stop
pr.set_value();
thr.join();
0
apophis42

また、promise/futureを使用して、条件付きスレッドやスレッドを気にする必要がないようにすることもできます。

#include <future>
#include <iostream>

struct MyClass {

    ~MyClass() {
        _stop.set_value();
    }

    MyClass() {
        auto future = std::shared_future<void>(_stop.get_future());
        _thread_handle = std::async(std::launch::async, [future] () {
            std::future_status status;
            do {
                status = future.wait_for(std::chrono::seconds(2));
                if (status == std::future_status::timeout) {
                    std::cout << "do periodic things\n";
                } else if (status == std::future_status::ready) {
                    std::cout << "exiting\n";
                }
            } while (status != std::future_status::ready);
        });
    }


private:
    std::promise<void> _stop;
    std::future<void> _thread_handle;
};


// Destructor
int main() {
    MyClass c;
    std::this_thread::sleep_for(std::chrono::seconds(9));
}
0
OznOg

または、condition_variableテクニックなしで同じことを達成する他の方法はありますか?

条件変数に代わるものの1つは、「実行中」フラグを確認し、それがあればスリープ状態に戻るために、はるかに定期的な間隔でスレッドを起こすことができますが設定されておらず、割り当てられた時間がまだ期限切れになっていません。

_void periodically_call(std::atomic_bool& running, std::chrono::milliseconds wait_time)
{
    auto wake_up = std::chrono::steady_clock::now();

    while(running)
    {
        wake_up += wait_time; // next signal send time

        while(std::chrono::steady_clock::now() < wake_up)
        {
            if(!running)
                break;

            // sleep for just 1/10 sec (maximum)
            auto pre_wake_up = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);

            pre_wake_up = std::min(wake_up, pre_wake_up); // don't overshoot

            // keep going to sleep here until full time
            // has expired
            std::this_thread::sleep_until(pre_wake_up);
        }

        SendStatusInfo(some_info); // do the regular call
    }
}
_

注:実際の待機時間は任意に設定できます。この例では、100ms std::chrono::milliseconds(100)にしました。停止するシグナルに対してスレッドがどれだけ反応するかによって異なります。

たとえば、あるアプリケーションでは、アプリケーションが終了時に閉じる前にすべてのスレッドが停止するのを1秒間待って満足していたので、その1秒を作成しました。

応答性は、アプリケーション次第です。起動時間が短いほど、CPUが消費します。ただし、数ミリ秒の非常に短い間隔であっても、おそらくCPU時間に関してはあまり登録されません。

0
Galik