停止长睡眠线程

Stopping long-sleep threads

假设我有一个线程应该定期执行一些任务,但这个周期是每小时 6 次 每小时 12 次(每 5 分钟),我经常看到使用 is_running 标志控制线程循环的代码,每个循环都会检查该标志,如下所示:

std::atomic<bool> is_running;

void start()
{
    is_running.store(true);
    std::thread { thread_function }.detach();
}

void stop()
{
    is_running.store(false);
}

void thread_function()
{
    using namespace std::literals;
    while (is_running.load())
    {
        // do some task...
        std::this_thread::sleep_for(5min);
    }
}

但是如果 stop() 函数被调用,比方说,在 start() 之后 1 毫秒,线程将存活 299999 毫秒,直到它醒来,检查标志,然后死掉。

我的理解对吗?如何避免使本应结束的线程保持活动状态(但处于休眠状态)?到目前为止,我最好的方法如下:

void thread_function()
{
    using namespace std::literals;
    while (is_running.load())
    {
        // do some task...
        for (unsigned int b = 0u, e = 1500u; is_running.load() && (b != e); ++b)
        {
            // 1500 * 200 = 300000ms = 5min
            std::this_thread::sleep_for(200ms);
        }
    }
}

有没有更简单、更直接的方法来实现这个目标?

是的,通过 std::mutexstd::lock_guardstd::conditional_variable :

std::mutex mtx;
std::conditional_variable cv;

void someThreadFunction (){
   while(!stopThreadFlag){
     std::lock_gurad<std::mutex> lg(mtx);
     cv.wait_for(lg,SOME_ITERVAL,!stopThreadFlag || wakeTheThreadVariable);
     //the rest here
  }
}

有两种传统方法可以做到这一点。

您可以对条件变量使用定时等待,并让其他线程向您的周期性线程发出信号,使其在时间到时唤醒并终止。

或者,您可以 poll 在管道上将睡眠作为超时而不是睡眠。然后你只需向管道写入一个字节,线程就会唤醒并可以退出。

使用条件变量。您等待条件变量 or 5 分钟过去了。记得检查虚假唤醒。

cppreference

我在 google 搜索的一两分钟内找不到关于如何使用条件变量的好的堆栈溢出 post。棘手的部分是意识到 wait 既不会在 5 分钟过去也不会发送信号的情况下醒来。处理这个问题的最干净的方法是使用带有 lambda 的等待方法,该 lambda 仔细检查唤醒是否是 "good"。

here 是 cppreference 上的一些示例代码,它使用 wait_until 和 lambda。 (wait_for 与 lambda 等同于 wait_until 与 lambda)。我稍微修改了一下。

这是一个版本:

struct timer_killer {
  // returns false if killed:
  template<class R, class P>
  bool wait_for( std::chrono::duration<R,P> const& time ) const {
    std::unique_lock<std::mutex> lock(m);
    return !cv.wait_for(lock, time, [&]{return terminate;});
  }
  void kill() {
    std::unique_lock<std::mutex> lock(m);
    terminate=true; // should be modified inside mutex lock
    cv.notify_all(); // it is safe, and *sometimes* optimal, to do this outside the lock
  }
  // I like to explicitly delete/default special member functions:
  timer_killer() = default;
  timer_killer(timer_killer&&)=delete;
  timer_killer(timer_killer const&)=delete;
  timer_killer& operator=(timer_killer&&)=delete;
  timer_killer& operator=(timer_killer const&)=delete;
private:
  mutable std::condition_variable cv;
  mutable std::mutex m;
  bool terminate = false;
};

live example.

您在共享地点创建了一个 timer_killer。客户端线程可以 wait_for( time )。如果它 return 是假的,这意味着你在等待完成之前就被杀了。

控制线程只调用 kill(),每个执行 wait_for 的人都会得到一个 false return.

请注意,存在一些争用(互斥锁的锁定),因此这不适合无限线程(但很少有东西适合)。如果您需要无限数量的任务 运行 具有任意延迟而不是每个延迟重复任务的完整线程,请考虑使用调度程序——每个实际线程都超过 1 兆字节的系统地址 space 使用(仅用于堆栈)。