您可以仅使用标准c ++ / c ++ 11来实现没有“睡眠”功能的计时器吗?

时间:2018-08-13 07:17:30

标签: c++ c++11 timer

我有以下代码(手工复制):

// 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;
    })
}

我不想对所写类的细节感到困惑,所以这是一个非常精简的版本。完整类调用函数回调,并具有取消计时器等的变量。

我只想关注“睡眠”部分。我可以不睡觉而实施这样的事情吗,或者有更好的方法吗? -还是睡得很好? -我认为睡眠通常是不良设计的标志(我读过一些地方)...但是我想不出一种没有计时器的方法来实现计时器:(

其他说明:计时器应具有能够随时停止/唤醒的要求。只是为了清楚起见才添加它,因为它似乎会影响采用哪种解决方案。在我的原始代码(不是此代码段)中,我使用了可以脱离循环的原子布尔标志。

3 个答案:

答案 0 :(得分:33)

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();

答案 1 :(得分:6)

虽然代码可以“运行”,但对于作为计时器的预期目的而言,它不是最佳的。

存在std::this_thread::sleep_until,根据实现的不同,可能仅在进行一些数学运算后仅调用sleep_for,但是可能会使用一个合适的计时器,在精度和可靠性方面要优越得多。

通常,睡眠不是最好,最可靠和最准确的方法,但是有时候,如果只打算等待一些大概的时间,那可能就足够了。

无论如何,像您的示例中那样反复少量睡眠是一个坏主意。这将在不必要的重新调度和唤醒线程上消耗大量CPU,并且在某些系统上(尤其是Windows,尽管Windows 10在这方面不再那么糟糕),它可能会增加大量的抖动和不确定性。请注意,不同的Windows版本舍入调度程序的粒度不同,因此,除了通常不太精确外,您甚至没有一致的行为。对于一个大的等待,舍入几乎是“谁在乎”,但是对于一系列的小等待,这是一个严重的问题。

除非必须提前中止计时器的功能(但在这种情况下,也有更好的实现方法!),您应该只睡一次,再也不要睡了。完整的持续时间。为了正确起见,您应该检查自己是否确实有预期的时间,因为某些系统(尤其是POSIX)可能处于睡眠不足状态。

过度睡眠是另一个需要解决的问题,正确,因为即使正确检查并检测到这种情况,一旦发生,您也无能为力(时间已经过去了) ,再也不会回来)。但是,a,这只是睡觉的根本弱点,您无能为力。幸运的是,大多数人大部分时间都可以忽略这个问题。

答案 2 :(得分:3)

可以忙等待循环检查时间,直到达到您等待的时间为止。这显然很可怕,所以不要这样做。睡眠10ms会更好一些,但绝对是糟糕的设计。 (@Damon's answer has some good info。)


使用函数名称中带有sleep的函数没有什么问题,如果那是您的程序此时最有用的功能。

避免使用sleep的建议可能是针对短时间睡眠的一般模式,先检查是否有事要做,然后再次睡眠。相反,请阻止没有超时或超时很长的事件。 (例如,GUI应通过调用阻止函数来等待按键/单击,并设置超时以在需要自动保存或接下来发生任何将来的事情时将其唤醒。通常,您不需要单独的线程只是进入睡眠状态,但是如果没有合适的位置插入当前时间的支票,您可能会这么做。)

让操作系统最终使您醒来的时间变得更好了。这样可以避免上下文切换和缓存污染减慢其他程序的速度,并在您短暂睡眠时浪费功率。

如果您知道一段时间内无事可做,那就睡一会儿。 AFAIK,多次短睡眠不会提高Windows,Linux或OS X等主流操作系统的唤醒时间的准确性。如果您的代码经常被唤醒,则可以避免使用指令缓存,但是如果这样的话延迟是一个实际问题,您可能需要实时操作系统和更复杂的计时方法。

如果有的话,长时间处于休眠状态的线程更有可能在请求时准确唤醒,而最近运行且仅睡眠10ms的线程可能会遇到调度程序时间片问题。在Linux上,已经休眠了一段时间的线程在唤醒时会获得优先级提升。


使用名称中没有sleep的函数阻塞1秒并不比使用sleepthis_thread::sleep_for好。

(显然,您希望另一个线程能够唤醒您。此要求已埋在问题中,但是是的,条件变量是实现此目的的一种可移植的好方法。)


如果您想使用纯ISO C ++ 11,则最好选择std::this_thread::sleep_forstd::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或手动设置)实现的,如果与{{1 }}。


睡眠陷阱:迟/早醒

关于可能会睡得太久的警告也同样适用于steady_clocksleep,或者其他任何特定于操作系统的睡眠或超时(包括@Sebastian答案中的条件变量方法)。这是不可避免的。不过,实时操作系统可以为您带来额外的延迟。

您已经用10毫秒的睡眠时间做了类似的事情:

始终假设nanosleep或其他任何功能醒来得很晚,并检查当前时间,而不是在任何重要情况下都使用静默应答。

不能使用重复的sleep构建可靠的时钟。   例如不要建立一个倒计时计时器,该计时器睡眠1秒钟,然后递减并显示一个计数器,然后再睡眠1秒钟。除非它只是一个玩具,而且您不太在乎准确性。

某些睡眠功能(例如POSIX sleep)也可以在信号上提早唤醒。如果过早醒来是正确性问题,请检查时间,并在必要的情况下重新进入睡眠时间以计算出间隔。 (或使用sleep(3)