是否可以仅使用标准 c++/c++11 实现不带"sleep"的计时器?
Can you implement a timer without a "sleep" in it using standard c++/c++11 only?
重要更新
注意:由于这个问题是专门关于定时器的,因此需要注意的是,gcc中有一个错误,即如果您使用std::condition_variable::wait_fo(或wait_util),即使您给它传递std::chrono::steady_clock时间点,它也会使用系统时钟。这意味着计时器不是单调的——也就是说,如果你将系统时间向前更改一天,那么你的计时器可能不会触发一天+你的超时——如果你将时间向后更改,你的计时器可以立即触发。
请参阅:等待直到系统时间更改的condition_variable变通方法
这个错误的修复程序进入了gcc v10+
结束
我有以下代码(手工复制):
// Simple stop watch class basically takes "now" as the start time and
// returns the diff when asked for.
class stop_watch {...}
// global var
std::thread timer_thread;
void start_timer(int timeout_ms)
{
timer_thread = std::thread([timeout_ms, this](){
stop_watch sw;
while (sw.get_elapsed_time() < timeout_ms)
{
// Here is the sleep to stop from hammering a CPU
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Do timeout things here...
std::cout << "timed out!" << std::endl;
})
}
我不想太拘泥于我写的课的细节,所以这是一个非常精简的版本。完整类调用一个函数回调,并有一个变量来取消计时器等。
我只是想专注于";睡眠;部分我能在不睡觉的情况下实现这样的东西吗?或者有更好的方法吗?-还是睡眠很好?-我认为睡眠通常是糟糕设计的标志(我在一些地方读过)。。。但我想不出一种没有定时器的方法来实现定时器:(
附加说明:计时器应具有能够随时停止/唤醒的要求。只是为了清楚起见,添加了这一点,因为它似乎会影响选择什么样的解决方案。在我的原始代码(而不是这个片段)中,我使用了一个可以脱离循环的原子布尔标志。
C++11为我们提供了std::condition_variable
。在你的计时器中,你可以等待,直到你的条件得到满足:
// Somewhere else, e.g. in a header:
std::mutex mutex;
bool condition_to_be_met{false};
std::condition_variable cv;
// In your timer:
// ...
std::unique_lock<std::mutex> lock{mutex};
if(!cv.wait_for(lock, std::chrono::milliseconds{timeout_ms}, [this]{return condition_to_be_met;}))
std::cout << "timed out!" << std::endl;
您可以在此处找到更多信息:https://en.cppreference.com/w/cpp/thread/condition_variable
要发出条件已满足的信号,请在另一个线程中执行此操作:
{
std::lock_guard<std::mutex> lock{mutex}; // Same instance as above!
condition_to_be_met = true;
}
cv.notify_one();
虽然您的代码可以"工作",但对于定时器的预期用途来说,它是次优的。
存在std::this_thread::sleep_until
,根据实现的不同,可能在做了一些数学运算后才调用sleep_for
,但可以使用适当的计时器,这在精度和可靠性方面非常优越。
一般来说,睡觉不是最好、最可靠、最准确的事情,但有时,如果只是等待一些大致的时间,那就"足够好"了。
无论如何,像你的例子中那样,重复少量睡眠是个坏主意。这将在不必要的重新调度和唤醒线程时消耗大量CPU,在某些系统上(尤其是Windows,尽管Windows 10在这方面已经不那么糟糕了),可能会增加相当大的抖动和不确定性。请注意,不同的Windows版本对调度程序的粒度的调整方式不同,因此,除了通常不会过于精确之外,您甚至没有一致的行为。舍入在很大程度上是"谁在乎"一个大的等待,但对于一系列小的等待来说,这是一个严重的问题。
除非有必要提前中止计时器(但在这种情况下,也有更好的方法来实现这一点!),否则您应该在整个持续时间内只睡一次,而不是更多。为了确保正确性,您应该检查您是否确实获得了预期的时间,因为有些系统(尤其是POSIX)可能处于睡眠状态。
如果你需要的话,过度睡眠是一个不同的问题,因为即使你正确地检查和检测了这种情况,一旦发生,你也无能为力(时间已经过去了,再也不会回来了)。但遗憾的是,这只是睡眠的一个根本弱点,你无能为力。幸运的是,大多数人在大多数时候都能摆脱这个问题。
您可以在循环中忙于等待检查时间,直到它达到您等待的时间。这显然很可怕,所以不要这么做。睡10毫秒有点好,但绝对是糟糕的设计。(@Damon的回答有一些好的信息。)
如果以sleep
的名义使用函数对您的程序来说是最有用的,那么这并没有错
避免sleep
的建议可能是反对短时间睡眠的一般模式,检查是否有什么事情可做,然后再睡。相反,阻止等待一个没有超时或超时时间很长的事件。(例如,GUI应该通过调用阻塞函数来等待按键/点击,并设置一个超时,以便在自动保存或下一步发生的任何事情时将其唤醒。您通常不需要单独的线程来睡眠,但如果没有合理的插入当前时间的检查,则可以。)
当终于到了做任何事情的时候,让操作系统唤醒你会更好。这样可以避免上下文切换和缓存污染,从而在睡眠不足时减慢其他程序的运行速度并浪费电源
如果你知道一段时间内没有什么可做的,就睡那么久,只睡一次。AFAIK,在Windows、Linux或OS X等主流操作系统上,多次短暂睡眠不会提高唤醒时间的准确性。如果你的代码经常唤醒,你可能会避免指令缓存丢失,但如果延迟量是一个真正的问题,你可能需要一个实时操作系统和一种更复杂的计时方法。(比如提前一秒钟醒来,然后旋转等待。)
如果有什么不同的话,一个睡眠了很长时间的线程更有可能在它请求的时候醒来,而一个最近运行但只睡了10ms的线程可能会遇到调度程序时间片问题。在Linux上,休眠了一段时间的线程在醒来时会获得优先级提升。
以阻止1秒的名称使用不带sleep
的函数并不比使用sleep
或this_thread::sleep_for
好。
(显然,你希望另一个线程能够唤醒你。这个要求被隐藏在问题中,但是的,条件变量是一个很好的可移植方法。)
如果您想使用纯ISO C++11,那么std::this_thread::sleep_for
或std::this_thread::sleep_until
是您的最佳选择。它们在标准标头<thread>
中定义。
sleep(3)
是POSIX函数(与nanosleep
类似),而不是ISO C++11的一部分。如果这对你来说不是问题,那么如果合适的话,可以随意使用。
对于一段时间内的便携式高精度操作系统辅助睡眠,C++11引入了std::this_thread::sleep_for(const std::chrono::duration<Rep, Period> &sleep_duration)
(cppreference页面有一个使用它的代码示例。)
阻止的当前线程执行至少指定的sleep_duration。
由于以下原因,此函数可能会阻塞超过sleep_duration的时间调度或资源争用延迟。
该标准建议使用稳定的时钟来测量期间如果实现使用系统时钟,则等待时间也可能对时钟调整敏感。
要休眠直到时钟到达指定时间(可能考虑到系统时间的更改/校正):
std::this_thread::sleep_until(const std::chrono::time_point<Clock,Duration>& sleep_time)
在指定
sleep_time
之前阻止当前线程的执行已联系到。使用绑定到
sleep_time
的时钟,这意味着时钟被考虑在内。因此,块的持续时间可能,但可能不会,小于或大于sleep_time - Clock::now()
在呼叫时,取决于调整的方向。该功能也可以阻塞超过sleep_time
之后的时间由于调度或资源争用延迟而达到。
请注意,sleep_for
不受系统时钟变化的影响,因此它会实时休眠。
但是sleep_until
应该让你在系统时钟到达给定时间时醒来,即使它是通过调整(NTP或手动设置)来实现的,如果与steady_clock
以外的时钟一起使用。
睡眠问题:晚睡/早起
当然,关于可能睡得太久的警告也适用于sleep
和nanosleep
,或者任何其他特定于操作系统的睡眠或超时(包括@Sebastian回答中的条件变量方法)。这是不可避免的;不过,实时操作系统可以为您提供额外延迟的上限。
你已经在用10毫秒的睡眠做这样的事情了:
始终假设sleep
或任何其他功能醒来较晚,并检查当前时间,而不是在任何重要的情况下使用航位推算
您不能从重复的sleep
中构建可靠的时钟。例如,不要建立一个倒计时计时器,它会休眠1秒,递减并显示计数器,然后再休眠一秒钟。除非它只是一个玩具,而且你不太关心准确性。
一些睡眠功能,如POSIXsleep(3)
,也可以在收到信号后提前醒来。如果过早醒来是一个正确性问题,请检查时间,如有必要,在计算的时间间隔内重新入睡。(或使用sleep_until
)
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- boost::asio::steady_timer()与sleep()我应该使用哪一个
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 在c++中实现LinkedList时,应出现未处理的错误
- 为左值和右值的包装器实现C++范围
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- 使用GSoap实现ONVIF
- 在用于格式4的arm模拟器中实现功能时的一个问题
- 用于AVX的ln(x)的实现,m256
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 是否可以仅使用标准 c++/c++11 实现不带"sleep"的计时器?