假设我有一个应该定期执行某项任务的线程,但这段时间每小时 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);
}
}
}
有没有更简单,更直接的方法来实现这一目标?
答案 0 :(得分:20)
使用条件变量。等待条件变量或传递5分钟。记得检查是否有虚假的唤醒。
关于如何在一两分钟的谷歌搜索中使用条件变量,我找不到一个好的堆栈溢出帖子。棘手的部分是意识到wait
可以在5分钟过去时醒来,也不会发出信号。处理这个问题最干净的方法是使用带有lambda的wait方法,该方法可以仔细检查唤醒是否是一个好的&#34;之一。
here是cppreference上的一些示例代码,它使用带有lambda的wait_until
。 (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;
};
您在共享位置创建timer_killer
。客户端线程可以wait_for( time )
。如果它返回false,则表示在等待完成之前你已经被杀死了。
控制线程只调用kill()
,而wait_for
的每个人都会获得false
次返回。
请注意,存在一些争用(锁定互斥锁),因此这不适用于无限线程(但很少有东西)。如果您需要使用任意延迟运行的无限数量的任务而不是每个延迟重复任务的完整线程,请考虑使用调度程序 - 每个实际线程都超过一兆字节的系统地址空间(仅用于堆栈)
答案 1 :(得分:2)
有两种传统方法可以做到这一点。
你可以在一个条件变量上使用定时等待,并让另一个线程发出信号通知你的周期性线程在它的时间内被唤醒并死掉。
或者,您可以在管道上poll
将睡眠作为超时而不是睡眠。然后你只需要向管道写一个字节,线程就会唤醒并退出。
答案 2 :(得分:-2)
是的,通过std::mutex
,std::lock_guard
和std::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
}
}