如何唤醒正在睡觉的 std::thread

How to wake a std::thread while it is sleeping

我正在使用 C++11,我有一个 std::thread 是 class 成员,它每 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 逻辑添加到所需位置是否可以实现目标?

环境:
Linux 和带有编译器 gcc 和 clang 的 Unix。

有一个悲伤但真实的事实 - 您正在寻找的是一个信号,而 Posix 线程没有真正的信号机制。

此外,与任何类型的计时相关的唯一 Posix 线程原语是条件变量,这就是您的在线搜索将您引向它的原因,并且由于 C++ 线程模型大量构建于 Posix API,在标准 C++ 中,Posix-compatible primitives is all you get.

除非你愿意走出 Posix(你没有指明平台,但有一些本地平台方法可以处理不受这些限制的事件,特别是 eventfd Linux) 你将不得不坚持使用条件变量,是的,使用条件变量需要一个互斥锁,因为它内置于 API.

你的问题没有特别要求代码示例,所以我没有提供任何代码示例。如果你想要一些,请告诉我。

How can I use an std::condition_variable to wake the thread and exit gracefully while it was sleeping? Or are there any other ways of achieving the same without condition_variable technique?

不,从 C++17 开始在标准 C++ 中不存在(当然有非标准的、特定于平台的方法可以做到这一点,并且很可能将某种信号量添加到 C++2a 中) .

Additionally, I see that I need to use a std::mutex in conjunction with std::condition_variable? Is that really necessary?

是的。

Is it not possible to achieve the goal by adding the std::condition_variable logic only to required places in the code piece here?

没有。首先,如果不锁定互斥量(并将锁定对象传递给等待函数),就不能等待 condition_variable,因此无论如何都需要有一个互斥量。因为无论如何你都必须有一个互斥锁,所以要求服务员和通知者都使用那个互斥锁并不是什么大问题。

条件变量受制于"spurious wake ups",这意味着它们可以无缘无故地停止等待。为了判断它是因为收到通知而醒来,还是被虚假地唤醒,您需要一些由通知线程设置并由等待线程读取的状态变量。因为该变量由多个线程共享,所以需要安全地访问它,互斥锁确保了这一点。

即使您为共享变量使用原子变量,您通常仍需要互斥锁来避免错过通知。

这一切都在更详细的解释 https://github.com/isocpp/CppCoreGuidelines/issues/554

How can I use an std::condition_variable to wake the thread and exit gracefully while it was sleeping?

您使用std::condition_variable::wait_for() instead of std::this_thread::sleep_for() and first one can be interrupted by std::condition_variable::notify_one() or std::condition_variable::notify_all()

Additionally, I see that I need to use a std::mutex in conjunction with std::condition_variable? Is that really necessary? Is it not possible to achieve the goal by adding the std::condition_variable logic only to required places in the code piece here?

是的,有必要将 std::mutexstd::condition_variable 一起使用,你应该使用它而不是制作你的标志 std::atomic 因为尽管标志本身具有原子性,你仍然会在你的代码,您会注意到如果您不在此处使用互斥锁,有时您的休眠线程会错过通知。

使用 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;
};

Additionally, I see that I need to use a std::mutex in conjunction with std::condition_variable? Is that really necessary? Is it not possible to achieve the goal by adding the std::condition_variable logic only to required places in the code piece here?

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()让它完成。

Live example:

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";
}

Or are there any other ways of achieving the same without condition_variable technique?

条件变量的另一种选择是您可以更规律地唤醒您的线程以检查"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 时间方面也可能不会记录太多。

Or are there any other ways of achieving the same without the condition_variable technique?

在这种情况下,您可以使用 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(std::chrono::minutes(2)) != std::future_status::timeout)
            return;
    }
}};
//When ready to stop
pr.set_value();
thr.join();

您也可以使用 promise/future 这样您就不需要为条件 and/or 线程而烦恼:

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