使用 std::atomic 与 std::condition_variable 在 C++ 中暂停和恢复 std::thread 的方法

Approach of using an std::atomic compared to std::condition_variable wrt pausing & resuming an std::thread in C++

本文关键字:std 恢复 暂停 方法 thread atomic condition 使用 variable C++      更新时间:2023-10-16

这是一个单独的问题,但与我在这里问的上一个问题有关

我在我的C++代码中使用std::thread来不断轮询一些数据&将其添加到缓冲区中。我使用C++ lambda来启动线程,像这样:

StartMyThread() {
    thread_running = true;
    the_thread = std::thread { [this] {
        while(thread_running) {
          GetData();
        }
    }};
}

thread_running是在类头中声明的atomic<bool>。下面是我的GetData函数:

GetData() {
    //Some heavy logic
}

接下来我还有一个StopMyThread函数,我将thread_running设置为false,以便它退出lambda block中的while循环。

StopMyThread() {
  thread_running = false;
  the_thread.join();
}

据我所知,我可以暂停&使用std::condition_variable恢复线程,如我之前的问题所指出的。

但是如果我只使用std::atomic<bool> thread_running来执行或不执行GetData()中的逻辑,是否有缺点?

GetData() {
    if (thread_running == false)
      return;
    //Some heavy logic
}

与这里所描述的使用std::condition_variable的方法相比,这会消耗更多的CPU周期吗?

当您想要有条件地停止另一个线程时,条件变量非常有用。所以你可能有一个一直在运行的"工作"线程,当它注意到没有什么事情要做时,它会等待。

原子解决方案需要你的UI交互与工作线程同步,或者非常复杂的逻辑来异步完成。

作为一般规则,你的UI响应线程不应该阻塞工作线程的非就绪状态。

struct worker_thread {
  worker_thread( std::function<void()> t, bool play = true ):
    task(std::move(t)),
    execute(play)
  {
    thread = std::async( std::launch::async, [this]{
      work();
    });
  }
  // move is not safe.  If you need this movable,
  // use unique_ptr<worker_thread>.
  worker_thread(worker_thread&& )=delete;
  ~worker_thread() {
    if (!exit) finalize();
    wait();
  }
  void finalize() {
    auto l = lock();
    exit = true;
    cv.notify_one();
  }
  void pause() {
    auto l = lock();
    execute = false;
  }
  void play() {
    auto l = lock();
    execute = true;
    cv.notify_one();
  }
  void wait() {
    Assert(exit);
    if (thread)
      thread.get();
  }
private:
  void work() {
    while(true) {
      bool done = false;
      {
        auto l = lock();
        cv.wait( l, [&]{
          return exit || execute;
        });
        done = exit; // have lock here
      }
      if (done) break;
      task();
    }
  }
  std::unique_lock<std::mutex> lock() {
     return std::unique_lock<std::mutex>(m);
  }
  std::mutex m;
  std::condition_variable cv;
  bool exit = false;
  bool execute = true;
  std::function<void()> task;
  std::future<void> thread;
};

或somesuch。

拥有一个线程。只要线程处于play()模式,线程就会重复运行task。如果在下一次task()完成时执行pause(),则工作线程停止。如果在task()调用结束之前执行play(),它不会注意到pause() .

唯一的等待是在销毁worker_thread时,它会自动通知工作线程它应该退出,并等待它完成。

您也可以手动设置.wait().finalize().finalize()是异步的,但如果你的应用程序正在关闭,你可以提前调用它,给工作线程更多的时间来清理,而主线程清理其他地方的东西。

.finalize()不能反转

未测试代码

除非我遗漏了什么,否则您已经在最初的问题中回答了这个问题:您将在每次需要时创建和销毁工作线程。在实际应用程序中,这可能是一个问题,也可能不是。

要解决两个不同的问题,这可能取决于您实际在做什么。一个问题是"我想让线程一直运行,直到我让它停止。"另一种情况似乎是"我有一个生产者/消费者对,并且希望能够在数据准备好时通知消费者"。thread_runningjoin方法适用于第一种方法。其次,您可能希望使用互斥锁和条件,因为您要做的不仅仅是使用状态来触发工作。假设你有一个vector<Work>。你用互斥锁来保护它,所以条件变成了[&work] (){ return !work.empty(); }或类似的东西。当wait返回时,您持有互斥锁,这样您就可以从工作中取出事情并执行它们。完成后,返回等待,释放互斥锁,以便生产者可以向队列中添加内容。

您可能想要结合这些技术。拥有一个"完成处理"的原子,所有线程都会定期检查它,以知道何时退出,以便您可以加入它们。使用条件来覆盖线程之间的数据传递情况。