Как разбудить std:: поток во время сна
Я использую 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 ответов
рабочий пример для вас, используя 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;
};
как я могу использовать
std::condition_variable
разбудить нить и грациозно выйти, пока она спала? Или есть какие-либо другие способы достижения того же самого безcondition_variable
технику?
нет, не в стандартном C++ С C++17 (конечно, есть нестандартные, специфические для платформы способы сделать это, и, вероятно, какой-то семафор будет добавлен в C++2a).
кроме того, я вижу, что мне нужно использовать
std::mutex
в сочетании сstd::condition_variable
? Заключаться в том действительно необходимо?
да.
невозможно ли достичь цели, добавив
std::condition_variable
логика только для требуемых мест в части кода здесь?
нет. Для начала, вы не можете ждать condition_variable
без блокировки мьютекса (и передачи объекта блокировки в функцию ожидания), поэтому вам все равно нужно иметь мьютекс. Поскольку вы все равно должны иметь мьютекс, требуя, чтобы официант и уведомитель использовали этот мьютекс не такой крупная сделка.
переменные состояния подвержены "ложным пробуждениям", что означает, что они могут перестать ждать без причины. Чтобы сказать, проснулся ли он, потому что он был уведомлен или проснулся спонтанно, вам нужна переменная состояния, которая устанавливается уведомляющим потоком и читается ожидающим потоком. Поскольку эта переменная совместно используется несколькими потоками, к ней необходимо безопасно обращаться, что обеспечивает мьютекс.
даже если вы используете атомарную переменную для переменной share, вы все равно обычно требуется мьютекс, чтобы избежать пропущенных уведомлений.
это все объясняется более подробно https://github.com/isocpp/CppCoreGuidelines/issues/554
как я могу использовать 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
поскольку, несмотря на атомарность самого флага, у вас будет состояние гонки в вашем коде, и вы заметите, что иногда ваш спящий поток пропустит уведомление, если вы не будете использовать мьютекс здесь.
есть печальный, но истинный факт-то, что вы ищете, - это сигнал, а потоки Posix не имеют истинного механизма сигнализации.
кроме того, единственный примитив потоковой передачи Posix, связанный с любым типом синхронизации,-это условная переменная, поэтому ваш онлайн-поиск приведет вас к ней, и поскольку модель потоковой передачи c++ сильно построена на Posix API, в стандартных примитивах C++ Posix-совместимых-это все, что вы получаете.
Если вы не хотите выходить за пределы Posix (вы делаете не указывайте платформу, но есть собственные способы работы с событиями, которые свободны от этих ограничений, в частности eventfd
в Linux) вам придется придерживаться переменных условий и да, работа с переменной условия требует мьютекса, так как он встроен в API.
ваш вопрос специально не запрашивает образец кода, поэтому я его не предоставляю. Дай мне знать,если захочешь.
кроме того, я вижу, что мне нужно использовать 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
где-то как-нить(с) это хочет подождать, и код, который хочет прервать, может это увидеть.
ожидающие потоки делают
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 техники?
одна альтернатива переменной условие вы можете разбудить свой поток с гораздо более регулярными интервалами, чтобы проверить флаг" работает " и вернуться в сон, если он не установлен, и отведенное время еще не истекло:
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
времени.
или есть ли другие способы достижения того же самого без метода 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();
вы также можете использовать 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));
}