我试图以固定的时间间隔执行一段代码。我有一些基于裸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
永远等待,以防timer
在worker
仍在等待时完成。
我是否需要running
标志,或者是否有更好的方式将break
排除在循环之外?
PS:我知道还有其他方法可以确保固定的时间间隔,我知道我当前的方法存在一些问题(例如,如果worker
需要的时间多于{timer
所用的时间间隔。 1}})。但是,我想先了解一段代码,然后再改变它。
答案 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_one
或notify_all
调用的线程没有放弃互斥锁(例如mutex::unlock()
或condition_variable::wait()
),则唤醒线程将无法运行。 在timer()
线程中,在notify_one()
调用后解锁互斥锁,因为范围结束并且guard
对象被销毁(析构函数隐式调用mutex::unlock()
)
允许编译器缓存变量的值。因此,将running
设置为true
可能不起作用,因为可能会缓存变量的值。为避免这种情况,您需要将running
声明为volatile
或std::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
会阻止执行。