Как разбудить std :: thread во время сна

Я использую С++ 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::mutex в сочетании с std::condition_variable? Это действительно необходимо? Не удается ли достичь цели, добавив логику std::condition_variable только в требуемые места в коде здесь?

Среда:
Linux и Unix с компиляторами gcc и clang.

Ответы

Ответ 1

Рабочий пример для использования 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;
};

Ответ 2

Как я могу использовать std::condition_variable чтобы разбудить поток и изящно выйти во время сна? Или есть ли другие способы достижения того же без condition_variable техники?

Нет, не в стандарте C++, а в C++ 17 (конечно, это нестандартные, специфичные для платформы способы сделать это, и, вероятно, какой-то семафор будет добавлен к C++ 2a).

Кроме того, я вижу, что мне нужно использовать std::mutex в сочетании с std::condition_variable? Это действительно необходимо?

Да.

Невозможно достичь цели, добавив логику std::condition_variable только в требуемые места в фрагменте кода здесь?

Нет. Для начала вы не можете ждать на condition_variable без блокировки мьютекса (и передачи объекта блокировки функции ожидания), поэтому вам нужно иметь мьютекс, присутствующий в любом случае. Так как вы все равно должны иметь мьютекс, требуя, чтобы и официант, и уведомитель использовали этот мьютекс, не так уж и важны.

Переменные условий подвержены "ложным пробуждениям", что означает, что они могут перестать ждать без причины. Чтобы узнать, проснулся ли он, потому что он был уведомлен или проснулся ложно, вам нужна переменная состояния, которая задается уведомляющим потоком и считывается потоком ожидания. Поскольку эта переменная разделяется несколькими потоками, ее нужно безопасно получать, что обеспечивает мьютекс.

Даже если вы используете атомную переменную для переменной общего доступа, вам все равно, как правило, требуется мьютекс, чтобы избежать пропущенных уведомлений.

Все это подробно описано в https://github.com/isocpp/CppCoreGuidelines/issues/554

Ответ 3

Как я могу использовать std :: condition_variable, чтобы разбудить поток и изящно выйти во время сна?

Вы используете std::condition_variable::wait_for() вместо std::this_thread::sleep_for() и первый может быть прерван std::condition_variable::notify_one() или std::condition_variable::notify_all()

Кроме того, я вижу, что мне нужно использовать std :: mutex в сочетании с std :: condition_variable? Это действительно необходимо? Невозможно достичь цели, добавив логику std :: condition_variable только в требуемые места в фрагменте кода здесь?

Да, нужно использовать std::mutex с std::condition_variable и вы должны использовать его вместо того, чтобы сделать свой флаг std::atomic как, несмотря на атомарность самого флага, у вас будет условие гонки в вашем коде, и вы заметите, что иногда ваши спальная нить пропускала бы уведомление, если бы вы не использовали мьютекс здесь.

Ответ 4

Существует грустный, но истинный факт - то, что вы ищете, является сигналом, а потоки Posix не имеют истинного механизма сигнализации.

Кроме того, единственным примитивом Posix для потоковой передачи, связанным с любым типом синхронизации, является условная переменная, поэтому ваш онлайн-поиск приводит вас к этому, и поскольку модель потоковой обработки C++ в значительной степени основана на Posix API, в стандартном C++ Posix-совместимом примитивы - это все, что вы получаете.

Если вы не захотите выйти за пределы Posix (вы не указываете платформу, но есть собственные способы работы с событиями, которые не содержат ограничений, в частности eventfd в Linux), вам придется придерживаться переменных условий и да, работающих с переменной условия требуется мьютекс, поскольку он встроен в API.

В вашем вопросе конкретно не запрашивается образец кода, поэтому я не предоставляю никаких данных. Дай мне знать, если хочешь.

Ответ 5

Кроме того, я вижу, что мне нужно использовать std :: mutex в сочетании с std :: condition_variable? Это действительно необходимо? Невозможно достичь цели, добавив логику 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 где-нибудь, и те timed_waiter которые хотят подождать, и код, который хочет прервать, могут его увидеть.

Ожидающие потоки

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

для прерывания do 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";
}

Ответ 6

Или есть ли другие способы достижения того же без condition_variable техники?

Альтернативой переменной условия является то, что вы можете разбудить свою нить через гораздо более регулярные интервалы, чтобы проверить флаг "running" и вернуться в режим сна, если он не установлен, и выделенное время еще не истекло:

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). Это зависит от того, насколько отзывчив, вы хотите, чтобы поток был остановлен.

Например, в одном приложении я сделал это целую секунду, потому что я был рад за то, что мое приложение дождалось полной секунды, чтобы все потоки остановились, прежде чем он закрылся при выходе.

Насколько вам нужно реагировать, это зависит от вашего приложения. Чем короче время пробуждения, тем больше он потребляет CPU. Однако даже очень короткие интервалы в несколько миллисекунд, вероятно, не будут регистрироваться в терминах CPU времени.

Ответ 7

Или есть ли другие способы достижения этого без техники condition_variable?

В этом случае вы можете использовать std :: prom/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();

Ответ 8

Вы также можете использовать обещание/будущее, так что вам не нужно беспокоиться о conditionnal и/или потоках:

#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));
}