简体   繁体   English

你可以只使用标准的 c++/c++11 来实现一个没有“睡眠”的计时器吗?

[英]Can you implement a timer without a "sleep" in it using standard c++/c++11 only?

IMPORTANT UPDATE重要更新

Note: since this question is specifically about timers, its important to note there is a bug in gcc that if you are using std::condition_variable::wait_for (or wait_util) it uses the system clock even if you pass it a std::chrono::steady_clock time point.注意:由于这个问题专门针对计时器,因此需要注意 gcc 中存在一个错误,即如果您使用 std::condition_variable::wait_for(或 wait_util),它会使用系统时钟,即使您将其传递给 std:: chrono::steady_clock 时间点。 This means the timer is not monotonic - ie if you change the system time forward by a day then your timer may not trigger for a day + your timeout - if you change the time backwards your timer may trigger immediately.这意味着计时器不是单调的 - 即如果您将系统时间向前更改一天,那么您的计时器可能不会在一天内触发 + 您的超时 - 如果您向后更改时间,您的计时器可能会立即触发。

See: condition_variable workaround for wait_until with system time change请参阅: 使用系统时间更改 wait_until 的 condition_variable 解决方法

The fix for this bug went into gcc v10+这个错误的修复进入了 gcc v10+

END结尾

I have the following code (hand-copied in):我有以下代码(手工复制):

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

I did not want to get too bogged down in the detail of the class I writing so this is a very cut-down version.我不想被我所写的课程的细节所困扰,所以这是一个非常精简的版本。 The full class calls a function call-back and has a variable to cancel the timer etc...完整的类调用函数回调并有一个变量来取消计时器等......

I just wanted to focus on the "sleep" part.我只想专注于“睡眠”部分。 Can I implement something like this without a sleep or is there a better way to do it?我可以在不睡觉的情况下实现这样的事情还是有更好的方法来做到这一点? - or is sleep perfectly good? - 或者睡眠非常好? - I was of the opinion that sleeps are generally a sign of bad design (I have read that a few places)... but I can't think of a way to implement a timer without one :( - 我认为睡眠通常是糟糕设计的标志(我已经读过一些地方)......但我想不出一种方法来实现一个没有定时器的方法:(

Additional Note: The timer should have the requirement to be able to be stopped/woken at any time.附加说明:计时器应具有能够随时停止/唤醒的要求。 Just adding that for clarity because it appears to affect what kind of solution to go for.只是为了清楚起见而添加它,因为它似乎会影响要采用的解决方案。 In my original code (not this snippet) I used an atomic bool flag that can break out of the loop.在我的原始代码(不是这个片段)中,我使用了一个可以跳出循环的原子 bool 标志。

C++11 provides us with std::condition_variable . C++11 为我们提供了std::condition_variable In your timer you can wait until your condition has been met:在您的计时器中,您可以等到满足您的条件:

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

You can find more information here: https://en.cppreference.com/w/cpp/thread/condition_variable您可以在此处找到更多信息: https : //en.cppreference.com/w/cpp/thread/condition_variable

To signal that the condition has been met do this in another thread:要表示条件已满足,请在另一个线程中执行此操作:

{
    std::lock_guard<std::mutex> lock{mutex}; // Same instance as above!
    condition_to_be_met = true;
}
cv.notify_one();

While your code will "work", it is sub-optimal for the intended purpose as timer.虽然您的代码可以“工作”,但对于作为计时器的预期目的而言,它是次优的。

There exists std::this_thread::sleep_until which, depending on the implementation, possibly only just calls sleep_for after doing some math anyway, but which might use a proper timer, which is vastly superior in terms of precision and reliability.存在std::this_thread::sleep_until ,取决于实现,它可能只是在做一些数学运算后才调用sleep_for ,但它可能使用适当的计时器,这在精度和可靠性方面非常优越。

Generally, sleeping is not the best, most reliable, and most accurate thing to do, but sometimes, if just waiting for some approximate time is what's intended, it can be "good enough".一般来说,睡眠并不是最好、最可靠、最准确的做法,但有时,如果只是等待一些大致的时间是预期的,它可以“足够好”。

In any case, repeatedly sleeping for small amounts as in your example is a bad idea.无论如何,像您的示例中那样反复少量睡觉是个坏主意。 This will burn a lot of CPU on needlessly rescheduling and waking threads, and on some systems (Windows in particular, though Windows 10 isn't so bad in that respect any more) it may add a considerable amount of jitter and uncertainity.这将在不必要的重新调度和唤醒线程上消耗大量 CPU,并且在某些系统上(特别是 Windows,尽管 Windows 10 在这方面不再那么糟糕)它可能会增加大量的抖动和不确定性。 Note that different Windows versions round to the scheduler's granularity differently , so in addition to generally being not overly precise, you do not even have consistent behavior.请注意,不同的 Windows 版本对调度程序的粒度进行不同的舍入,因此除了通常不会过于精确之外,您甚至没有一致的行为。 Rounding is pretty much "who cares" for a single large wait, but it is a serious problem for a series of small waits.对于单个大等待,舍入几乎是“谁在乎”,但对于一系列小等待来说,这是一个严重的问题。

Unless the ability to abort the timer prematurely is a necessity (but in that case, there are better ways of implementing that, too!), you should sleep exactly once , never more, for the full duration.除非必须能够过早地中止计时器(但在这种情况下,也有更好的实现方法!),否则您应该在整个持续时间内只睡一次,绝不能多睡。 For correctness you should then check that you indeed got the time you expected because some systems (POSIX, notably) may under-sleep.为了正确起见,您应该检查您是否确实获得了预期的时间,因为某些系统(尤其是 POSIX)可能会睡眠不足。

Over-sleeping is a different problem if you need it right , because even if you check and detect that case correctly, once it has happened there's nothing you can do about it (time has already passed, and never comes back).过度睡眠是,如果你需要的是正确的,因为即使你检查并正确地检测这种情况下,一旦发生有什么可以做这件事(的时间已经过去了,一去不复返)不同的问题。 But alas, that's just a fundamental weakness of sleeping, not much you can do.但唉,这只是睡眠的一个根本弱点,你无能为力。 Luckily, most people can shrug this problem off, most of the time.幸运的是,大多数情况下,大多数人可以不理会这个问题。

You could busy-wait checking the time in a loop, until it reaches the time you're waiting for.可以忙等待检查循环中的时间,直到到达您等待的时间。 That's obviously horrible, so don't do it.这显然很可怕,所以不要这样做。 Sleeping for 10ms is somewhat better, but definitely poor design.睡眠 10 毫秒稍微好一些,但绝对是糟糕的设计。 ( @Damon's answer has some good info .) @Damon 的回答有一些很好的信息。)


There's nothing wrong with using functions with sleep in their name if that's the most useful thing for your program to do right then.如果您的程序此时要做的最有用的事情是使用其名称中带有sleep函数,那么使用函数并没有错。

The suggestion to avoid sleep is probably recommending against the general pattern of sleeping for a short time, checking if there's anything to do, then sleeping again.避免sleep的建议可能是建议反对短时间睡眠的一般模式,检查是否有任何事情要做,然后再次睡觉。 Instead, block waiting for an event with no timeout, or a very long timeout.相反,阻塞等待一个没有超时或很长时间超时的事件。 (eg a GUI should wait for a keypress / click by calling a blocking function, with a timeout set to wake it up when it's time to autosave or whatever future thing is coming up next. You normally don't need a separate thread just to sleep, but you might if there's nowhere sensible to insert checks for the current time.) (例如,GUI应该等待一个按键/调用阻塞函数单击,具有超时设置为唤醒它时,它的时间来自动保存或任何未来的事情是下一个来了。你通常不需要一个单独的线程只是睡眠,但如果没有明智的地方插入当前时间的检查,您可能会这样做。)

Letting the OS wake you up when it's finally time to do anything is much better.让操作系统在最终需要做任何事情时唤醒您好得多。 This avoids context switches and cache pollution slowing down other programs and wasting power while you're spinning on short sleeps.这可以避免上下文切换和缓存污染减慢其他程序的速度并在您短暂睡眠时浪费电力。

If you know there's nothing to do for some time, just sleep for that long with a single sleep.如果您知道有一段时间无事可做,那么只需一次睡眠就可以睡那么长时间。 AFAIK, multiple short sleeps won't improve the accuracy of the wake-up time on mainstream OSes like Windows, Linux, or OS X. You might avoid an instruction-cache miss if your code had been waking up frequently, but if that amount of delay is a real problem you probably need a real-time OS and a much more sophisticated approach to timing. AFAIK,多个短睡眠不会提高主流操作系统(如 Windows、Linux 或 OS X)上唤醒时间的准确性。如果您的代码经常被唤醒,您可能会避免指令缓存未命中,但如果该数量延迟是一个真正的问题,您可能需要实时操作系统和更复杂的计时方法。 (Like wake up a fraction of a second early and spin-wait from there.) (比如提前几分之一秒醒来,然后从那里旋转等待。)

If anything, a thread that's been sleeping for a long time is more likely to wake up exactly when it requested, while a thread that was running recently and slept for only 10ms might run into scheduler timeslice issues.如果有的话,一个长时间休眠的线程更有可能在它请求的时候准确地唤醒,而最近运行并且只休眠 10 毫秒的线程可能会遇到调度程序时间片问题。 On Linux, threads that have been asleep for a while get a priority boost when they do wake up.在 Linux 上,休眠一段时间的线程在唤醒时会获得优先级提升。


Using a function without sleep in the name that blocks for 1 second is no better than using sleep or this_thread::sleep_for .使用以阻塞 1 秒为名而没有sleep的函数并不比使用sleepthis_thread::sleep_for

(Apparently you want another thread to be able to wake you up. This requirement is buried in the question, but yes a condition variable is a good portable way to do that.) (显然,您希望另一个线程能够唤醒您。这个要求隐藏在问题中,但是条件变量是一种很好的可移植方式。)


If you want to use pure ISO C++11, then std::this_thread::sleep_for or std::this_thread::sleep_until are your best bet.如果你想使用纯 ISO C++11,那么std::this_thread::sleep_forstd::this_thread::sleep_until是你最好的选择。 These are defined in standard header <thread> .这些在标准头文件<thread>中定义。

sleep(3) is a POSIX function (like nanosleep ), not part of ISO C++11. sleep(3)是一个 POSIX 函数(如nanosleep ),而不是 ISO C++11 的一部分。 If that's not a problem for you, then feel free to use it if it's appropriate.如果这对您来说不是问题,那么在合适的情况下可以随意使用它。


For portable high-precision OS-assisted sleep-for-an-interval, C++11 introduced对于便携式高精度操作系统辅助睡眠间隔,引入了 C++11
std::this_thread::sleep_for(const std::chrono::duration<Rep, Period> &sleep_duration) (The cppreference page has a code example of using it.) std::this_thread::sleep_for(const std::chrono::duration<Rep, Period> &sleep_duration) (cppreference 页面有一个使用它的代码示例。)

Blocks the execution of the current thread for at least the specified sleep_duration.至少在指定的 sleep_duration 内阻止当前线程的执行。

This function may block for longer than sleep_duration due to scheduling or resource contention delays.由于调度或资源争用延迟,此函数可能会阻塞比 sleep_duration 更长的时间。

The standard recommends that a steady clock is used to measure the duration.该标准建议使用稳定的时钟来测量持续时间。 If an implementation uses a system clock instead, the wait time may also be sensitive to clock adjustments.如果实现使用系统时钟,则等待时间也可能对时钟调整敏感。


To sleep until a clock reaches a specified time (possibly taking into account changes / corrections to the system time):休眠直到时钟到达指定时间(可能考虑到系统时间的更改/更正):

std::this_thread::sleep_until(const std::chrono::time_point<Clock,Duration>& sleep_time)

Blocks the execution of the current thread until specified sleep_time has been reached.阻止当前线程的执行,直到达到指定的sleep_time

The clock tied to sleep_time is used, which means that adjustments of the clock are taken into account.使用与sleep_time相关的时钟,这意味着会考虑时钟的调整。 Thus, the duration of the block might, but might not, be less or more than sleep_time - Clock::now() at the time of the call, depending on the direction of the adjustment.因此,块的持续时间可能,但可能不会,小于或大于sleep_time - Clock::now() ,具体取决于调整的方向。 The function also may block for longer than until after sleep_time has been reached due to scheduling or resource contention delays.由于调度或资源争用延迟,该函数也可能会阻塞更长的时间,直到到达sleep_time之后。


Notice that sleep_for is meant to be unaffected by changes to the system clock, so it sleeps for that much real time.请注意, sleep_for旨在不受系统时钟更改的影响,因此它会实时休眠。

But sleep_until is supposed to let you wake up when the system clock reaches a given time, even if it did that by being adjusted (NTP or manual setting), if used with a clock other than steady_clock .但是sleep_until应该让您在系统时钟达到给定时间时醒来,即使它通过调整(NTP 或手动设置)来实现,如果与steady_clock以外的时钟steady_clock


Sleep gotchas: late / early wakeup睡眠问题:迟醒/早醒

The caveats about possibly sleeping too long also apply to sleep and nanosleep , or any other OS-specific sleep or timeout (including the condition-variable approach in @Sebastian's answer), of course.当然,关于可能睡眠时间过长的警告也适用于sleepnanosleep ,或任何其他特定于操作系统的睡眠或超时(包括nanosleep答案中的条件变量方法)。 It's unavoidable;这是不可避免的; a realtime OS can give you upper bounds on that extra delay, though.不过,实时操作系统可以为您提供额外延迟的上限。

You're already doing something like this with your 10ms sleeps:你已经在用你的 10ms 睡眠做这样的事情:

Always assume that sleep or whatever other function woke up late, and check the current time instead of using dead-reckoning in any case where that matters.始终假设sleep或任何其他功能很晚才醒来,并在任何重要的情况下检查当前时间而不是使用航位推算。

You can't build a reliable clock out of repeated sleep .无法从反复sleep构建出可靠的时钟。 eg don't build a count-down timer that sleeps for 1 second, decrements and displays a counter, then sleeps for another second.例如,不要建立一个倒数计时器,它会休眠 1 秒,递减并显示一个计数器,然后再休眠一秒。 Unless it's just a toy and you don't care much about accuracy.除非它只是一个玩具而你不太在意准确性。

Some sleep functions such as POSIX sleep(3) can also wake up early on a signal.一些睡眠函数,例如 POSIX sleep(3)也可以在收到信号时提前唤醒。 If waking too early is a correctness problem, check the time and if necessary go back to sleep for the calculated interval.如果太早醒来是一个正确性问题,请检查时间,并在必要时按照计算的间隔重新入睡。 (Or use sleep_until ) (或使用sleep_until

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM