我有一个运行循环的线程。 我需要每5毫秒运行一次该循环(误差为1毫秒)。 我知道Sleep()函数并不准确。
你有什么建议吗?
更新。 我不能用其他方法。 在循环结束时我需要某种形式的Sleep。 我也不想让CPU负载达到100%。
我有一个运行循环的线程。 我需要每5毫秒运行一次该循环(误差为1毫秒)。 我知道Sleep()函数并不准确。
你有什么建议吗?
更新。 我不能用其他方法。 在循环结束时我需要某种形式的Sleep。 我也不想让CPU负载达到100%。
我正在寻找一种适用于实时应用程序的轻量级跨平台睡眠函数(即具有高分辨率/高精度和可靠性)。这是我的研究结果:
调度基础知识
放弃CPU并重新获取它是昂贵的。根据这篇文章,Linux上的调度延迟可能在10-30ms之间。因此,如果您需要高精度地睡眠不到10ms,则需要使用特殊的操作系统特定API。通常的C ++ 11 std :: this_thread :: sleep_for不是高分辨率睡眠。例如,在我的计算机上,快速测试表明,当我要求它仅睡眠1ms时,它经常睡眠至少3ms。
Linux
最流行的解决方案似乎是nanosleep()API。但是,如果您想要< 2ms的高分辨率睡眠,则还需要使用sched_setscheduler调用将线程/进程设置为实时调度。如果不这样做,则nanosleep()的行为就像过时的usleep,其分辨率约为10ms。另一种可能性是使用alarms。
Windows
这里的解决方案是使用多媒体时间,正如其他人建议的那样。如果您想在Windows上模拟Linux的nanosleep(),请参考下面的方法(原始参考)。同样,请注意,如果您正在循环调用sleep(),则无需一遍又一遍地进行CreateWaitableTimer()。
#include <windows.h> /* WinAPI */
/* Windows sleep in 100ns units */
BOOLEAN nanosleep(LONGLONG ns){
/* Declarations */
HANDLE timer; /* Timer handle */
LARGE_INTEGER li; /* Time defintion */
/* Create timer */
if(!(timer = CreateWaitableTimer(NULL, TRUE, NULL)))
return FALSE;
/* Set timer properties */
li.QuadPart = -ns;
if(!SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE)){
CloseHandle(timer);
return FALSE;
}
/* Start & wait for timer */
WaitForSingleObject(timer, INFINITE);
/* Clean resources */
CloseHandle(timer);
/* Slept without problems */
return TRUE;
}
跨平台代码
这里是实现Linux、Windows和苹果平台上的sleep函数的time_util.cc代码。需要注意的是,它并未像我之前提到的那样使用sched_setscheduler设置实时模式,所以如果你想要使用小于2毫秒的时间,则需要进行额外的设置。另一个可以改进的地方是,在某些循环中反复调用CreateWaitableTimer会对Windows版本产生性能影响,你可以参考这里的示例来避免这种情况。
#include "time_util.h"
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <time.h>
# include <errno.h>
# ifdef __APPLE__
# include <mach/clock.h>
# include <mach/mach.h>
# endif
#endif // _WIN32
/**********************************=> unix ************************************/
#ifndef _WIN32
void SleepInMs(uint32 ms) {
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = ms % 1000 * 1000000;
while (nanosleep(&ts, &ts) == -1 && errno == EINTR);
}
void SleepInUs(uint32 us) {
struct timespec ts;
ts.tv_sec = us / 1000000;
ts.tv_nsec = us % 1000000 * 1000;
while (nanosleep(&ts, &ts) == -1 && errno == EINTR);
}
#ifndef __APPLE__
uint64 NowInUs() {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return static_cast<uint64>(now.tv_sec) * 1000000 + now.tv_nsec / 1000;
}
#else // mac
uint64 NowInUs() {
clock_serv_t cs;
mach_timespec_t ts;
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cs);
clock_get_time(cs, &ts);
mach_port_deallocate(mach_task_self(), cs);
return static_cast<uint64>(ts.tv_sec) * 1000000 + ts.tv_nsec / 1000;
}
#endif // __APPLE__
#endif // _WIN32
/************************************ unix <=**********************************/
/**********************************=> win *************************************/
#ifdef _WIN32
void SleepInMs(uint32 ms) {
::Sleep(ms);
}
void SleepInUs(uint32 us) {
::LARGE_INTEGER ft;
ft.QuadPart = -static_cast<int64>(us * 10); // '-' using relative time
::HANDLE timer = ::CreateWaitableTimer(NULL, TRUE, NULL);
::SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
::WaitForSingleObject(timer, INFINITE);
::CloseHandle(timer);
}
static inline uint64 GetPerfFrequency() {
::LARGE_INTEGER freq;
::QueryPerformanceFrequency(&freq);
return freq.QuadPart;
}
static inline uint64 PerfFrequency() {
static uint64 xFreq = GetPerfFrequency();
return xFreq;
}
static inline uint64 PerfCounter() {
::LARGE_INTEGER counter;
::QueryPerformanceCounter(&counter);
return counter.QuadPart;
}
uint64 NowInUs() {
return static_cast<uint64>(
static_cast<double>(PerfCounter()) * 1000000 / PerfFrequency());
}
#endif // _WIN32
这里可以找到另一个更完整的跨平台代码:链接。
另一个快速解决方案
你可能已经注意到,上面的代码不再是非常轻量级了。它需要包括Windows头文件以及其他一些可能并不理想的内容,特别是当你在开发仅有头文件的库时。如果你需要的睡眠时间小于2ms,并且你不是非常喜欢使用操作系统代码,那么你可以使用以下简单的解决方案,这是跨平台的,并且在我的测试中表现非常好。只需记住,现在你没有使用严重优化的操作系统代码,后者可能更擅长节省电力和管理CPU资源。
typedef std::chrono::high_resolution_clock clock;
template <typename T>
using duration = std::chrono::duration<T>;
static void sleep_for(double dt)
{
static constexpr duration<double> MinSleepDuration(0);
clock::time_point start = clock::now();
while (duration<double>(clock::now() - start).count() < dt) {
std::this_thread::sleep_for(MinSleepDuration);
}
}
相关问题
std::chrono::steady_clock
而不是high_resolution_clock
。否则,您的sleep_for()
可能会睡眠比预期的时间长得多。 - John Zwinck请不要使用旋转。使用标准方法可以达到所需的分辨率和准确度。
当系统中断周期设置为高频时,您可以将Sleep()
使用至约1毫秒的时间段。请参阅Sleep()的说明以获取详细信息,尤其是使用多媒体定时器和获取和设置计时器分辨率来获取有关如何设置系统中断周期的详细信息。
如果正确实现此方法,则可达到几微秒的可获得精度。
我怀疑您的循环还在做其他事情。因此,我怀疑您想要总周期为5毫秒,这将是您在循环中使用Sleep()
以及其他时间的总和。
对于这种情况,我建议使用可等待计时器对象,但是,这些计时器也依赖于多媒体定时器API的设置。我在这里概述了高精度定时相关函数的相关内容。有关高精度时间的更深入洞察力可以在此处找到。
要获得更准确和可靠的定时,您可能需要查看进程优先级类
和线程优先级
。关于Sleep()的精度的另一个答案在这里。
然而,能否精确获得5毫秒的Sleep()
延迟取决于系统硬件。一些系统允许您以每秒1024个中断的速度运行(由多媒体定时器API设置)。这对应一个周期为0.9765625毫秒。因此,最接近的时间是4.8828125毫秒。其他一些系统允许更接近的时间,特别是在提供高分辨率事件定时器
的硬件上,自Windows 7以来计时显著改进。请参见MSDN上的有关定时器的信息和高精度事件定时器。
总结:将多媒体定时器设置为最大频率并使用可等待定时器。
根据问题标签,我猜你是在使用Windows系统。可以看一下Multimedia Timers,它们可以提供高于1毫秒的精度。
另一个选择是使用自旋锁,但这基本上会使CPU核心保持最大使用率。
timeBeginPeriod
,则应在应用程序早期调用一次,并确保在应用程序结束时调用timeEndPeriod
函数”,因为“频繁的调用会显著影响系统时钟、系统功率使用和调度程序”。因此,如果您依赖于这种精度进行多次调用,则不应在每次调用之前和之后调整。 - ShadowRangertimeBeginPeriod
和 timeEndPeriod
函数似乎修改操作系统全局状态(而不仅仅是您自己的进程),并且文档似乎暗示一个没有被 timeEndPeriod
匹配的 timeBeginPeriod
甚至在进程死亡后也无法恢复,因此很容易(例如在调整时钟时发生段错误或以其他方式强制终止进程)意外地使系统时钟永久处于次优状态(或者至少直到您重新启动)。对于任何运行在电池上的设备来说都非常糟糕,因为增加的功耗会损害电池寿命。总的来说,这似乎不是一个好主意。 - ShadowRanger与其使用sleep,也许您可以尝试循环检查时间间隔,并在时间差为5毫秒时返回。该循环应该比sleep更准确。
然而,请注意精度并不总是可能的。CPU 可能会被另一个操作捆绑,因此可能会错过5毫秒。
Sleep()
延迟的误差为1毫秒。这并不难实现。而且他们也没有在误差规定中使用“around”这个词。这里有什么矛盾之处? - ArnoSleep(0)
来改善时间控制。而且自旋只在高优先级下可靠。 - Arno