Как разбудить std :: thread, пока он спит - PullRequest
0 голосов
/ 02 октября 2018

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

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

Ответы [ 8 ]

0 голосов
/ 02 октября 2018

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

#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 голосов
/ 02 октября 2018

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

Одна альтернатива условной переменной - вы можете разбудить свой поток на многоболее регулярные интервалы для проверки «запущенного» флага и возврата в спящий режим, если он не установлен и выделенное время еще не истекло:

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.

0 голосов
/ 02 октября 2018

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

Вы можете использовать std :: обещание / std ::будущее как более простая альтернатива 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 голосов
/ 02 октября 2018

Кроме того, я вижу, что мне нужно использовать 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 где-нибудь, и поток (ы), который хочет ждать, и код, который хочет прервать, могут видетьit.

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

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";
}
0 голосов
/ 02 октября 2018

Рабочий пример использования 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;
};
0 голосов
/ 02 октября 2018

Как я могу использовать переменную std :: condition_var для пробуждения потока и корректного выхода из него, пока он спит?

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

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

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

0 голосов
/ 02 октября 2018

Как я могу использовать 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

0 голосов
/ 02 октября 2018

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

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

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

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

...