正确使用std :: condition_variable来触发定时执行

时间:2017-04-19 11:53:48

标签: c++ multithreading c++11 condition-variable

我试图以固定的时间间隔执行一段代码。我有一些基于裸pthread的东西,现在我想用std::thread做同样的事情。

#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>

bool running;
std::mutex mutex;
std::condition_variable cond;

void timer(){
  while(running) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    std::lock_guard<std::mutex> guard(mutex);
    cond.notify_one();
  }
  cond.notify_one();
}

void worker(){
  while(running){
    std::unique_lock<std::mutex> mlock(mutex);
    cond.wait(mlock);
    std::cout << "Hello World" << std::endl;
    //... do something that takes a variable amount of time ...//
  }
}

int main(){
  running = true;
  auto t_work = std::thread(worker);
  auto t_time = std::thread(timer);
  std::this_thread::sleep_for(std::chrono::milliseconds(10000));

  running = false;
  t_time.join();
  t_work.join();
}

worker实际上做了一些需要不同时间的事情,但它应该以固定的时间间隔进行安排。它似乎有效,但我对此很新,所以有些事情对我来说并不清楚......

  • 为什么我需要mutex?我并没有真正使用条件,但只要timer发出信号,工作人员就应该完成它的工作。

  • 循环后,timer是否真的需要再次调用cond.notify_one()?这是从较旧的代码和iirc中获取的,其原因是为了防止worker永远等待,以防timerworker仍在等待时完成。

  • 我是否需要running标志,或者是否有更好的方式将break排除在循环之外?

PS:我知道还有其他方法可以确保固定的时间间隔,我知道我当前的方法存在一些问题(例如,如果worker需要的时间多于{timer所用的时间间隔。 1}})。但是,我想先了解一段代码,然后再改变它。

2 个答案:

答案 0 :(得分:2)

  

为什么我需要一个互斥量?我并没有真正使用条件,但只要计时器发出信号,工作人员就应该完成它的工作。

您需要互斥锁的原因是等待条件满足的线程可能会受到虚假唤醒。为了确保您的线程实际收到正确满足条件的通知,您需要检查并且应该在等待调用中使用lambda。并且为了保证在虚假唤醒之后但在检查变量之前不修改变量,您需要获取互斥锁,以便您的线程是唯一可以修改条件的线程。在您的情况下,这意味着您需要为工作线程添加一种方法来实际验证计时器是否已用完。

  

在循环之后,计时器是否真的需要再次调用cond.notify_one()?这是从较旧的代码和iirc中获取的,其原因是为了防止工作人员永远等待,以防计时器在工作人员仍在等待时完成。

如果在循环后不调用notify,则工作线程将无限期地等待。因此,要干净地退出程序,您应该实际调用notify_all()以确保等待条件变量的每个线程都被唤醒并且可以干净地终止。

  

我是否需要运行标志,还是有更好的方法来摆脱循环?

跑步旗是实现你想要的最干净的方式。

答案 1 :(得分:2)

让我们首先检查背景概念。

关键部分

首先,需要Mutex来相互排除对critical section的访问权限。通常,关键部分被视为共享资源。例如。一个队列,一些I / O(例如套接字)等。简单来说,Mutex用于再次保护共享资源Race Condition可以将资源置于未定义状态。

示例:生产者/消费者问题

队列应该包含一些要完成的工作项。可能有多个线程将一些工作项放入Queue(即生成items =&gt; Producer Threads)和多个使用这些项并执行smth的线程。对他们有用(=&gt;消费者线程)。

Put和Consume操作修改Queue(尤其是其存储和内部表示)。因此,当运行put或consume操作时,我们希望排除其他操作执行相同操作。这就是Mutex发挥作用的地方。在一个非常基本的星座中,只有一个线程(无论是生产者还是消费者)都可以访问互斥锁,即锁定它。存在一些其他更高级别的锁定原语以根据使用场景(例如ReaderWriter Locks

来增加吞吐量

条件变量的概念

condition_variable::notify_one唤醒当前正在等待的一个帖子。至少有一个线程必须等待这个变量:

  • 如果没有线程在等待此变量,则发布的事件将丢失。
  • 如果有一个等待线程,它会一旦锁定与条件变量关联的互斥锁就会被唤醒并开始运行。因此,如果发起notify_onenotify_all调用的线程没有放弃互斥锁(例如mutex::unlock()condition_variable::wait()),则唤醒线程将无法运行。

timer()线程中,在notify_one()调用后解锁互斥锁,因为范围结束并且guard对象被销毁(析构函数隐式调用mutex::unlock()

此方法的问题

取消和变量缓存

允许编译器缓存变量的值。因此,将running设置为true可能不起作用,因为可能会缓存变量的值。为避免这种情况,您需要将running声明为volatilestd::atomic<bool>

worker线程

您指出worker需要在某些时间间隔内运行,并且可能会运行不同的时间。 timer线程可以仅在worker线程完成后运行。为什么你需要另一个线程来测量时间?这两个线程始终作为一个线性块运行,并且没有 critical 部分! 为什么不在任务执行后放置所需的sleep调用并在时间结束后立即开始运行?事实证明只有std::cout是共享资源。但目前它是从一个线程使用的。否则,您需要一个互斥锁(没有条件变量)来保护仅写入cout

#include <thread>
#include <atomic>
#include <iostream>
#include <chrono>

std::atomic_bool running = false;

void worker(){
  while(running){
    auto start_point = std::chrono::system_clock::now();
    std::cout << "Hello World" << std::endl;
    //... do something that takes a variable amount of time ...//

    std::this_thread::sleep_until(start_point+std::chrono::milliseconds(1000));

  }
}

int main(){
  running = true;
  auto t_work = std::thread(worker);
  std::this_thread::sleep_for(std::chrono::milliseconds(10000));

  running = false;
  t_work.join();
}

注意:如果您的任务阻止的时间超过sleep_until的1000毫秒,则在工作线程中调用start_point会阻止执行。