【问题标题】:Stopping long-sleep threads停止长睡眠线程
【发布时间】:2015-06-28 18:54:48
【问题描述】:

假设我有一个线程应该定期执行某些任务,但这段时间每小时 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);
        }
    }
}

有没有更简单直接的方法来实现这一点?

【问题讨论】:

  • 每小时 6 次(每 5 分钟),每小时 12 次还是每 10 分钟? :)
  • @AlexandreLavoie 太失败了!谢谢,我会改正的! :)
  • en.cppreference.com/w/cpp/thread/condition_variable,见第一句话。不是在固定的时间内休眠,而是在这段时间内进入可发出信号的等待状态,这样其他线程仍然可以打断你
  • 另一个选项是boost::basic_waitable_timer
  • 你的线程应该每 5 分钟做一次工作,还是 5 分钟后第一次工作,然后在他完成工作后再睡 5 分钟?如果第一个是你的情况,那么我会(在 Windows 上)创建一个计时器,它每 5 分钟创建一个线程来做一些工作。当创建线程开销太大时,我会使用线程池。

标签: c++ multithreading c++11 thread-sleep


【解决方案1】:

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

cppreference

我在谷歌搜索的一两分钟内找不到关于如何使用条件变量的好的堆栈溢出帖子。棘手的部分是意识到wait 既不会经过 5 分钟,也不会在发送信号的情况下唤醒。处理此问题的最简洁方法是使用带有 lambda 的 wait 方法,该方法会仔细检查唤醒是否“良好”。

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

这是一个版本:

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 )。如果它返回 false,则表示您在等待完成之前就被杀死了。

控制线程只调用kill(),每个执行wait_for 的人都会得到false 返回。

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

【讨论】:

  • 在 kill 方法中 'std::unique_lock<:mutex>' 应该是 'std::lock_guard<:mutex>' 吗?
  • @user 为什么?我猜,但如果可以检测到,好处就很小。
  • 如果你不调用 lock.lock(),我认为它不会做任何事情。
  • @user1633272 不,唯一锁只是一个可移动的锁防护装置。您可以使用互斥锁创建解锁的唯一锁,但这不是创建它们的默认方式。
  • @kaip 已修复,还修改了 kill 以考虑 cv 的现代编译器优化。
【解决方案2】:

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

您可以在条件变量上使用定时等待,并让另一个线程向您的周期性线程发出信号,以便在该时间时唤醒并终止。

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

【讨论】:

    【解决方案3】:

    是的,通过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
      }
    }
    

    【讨论】:

    • 这不会编译。
    猜你喜欢
    • 1970-01-01
    • 2011-05-14
    • 2019-11-29
    • 1970-01-01
    • 2010-12-09
    • 1970-01-01
    • 2018-08-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多