需要精确的线程睡眠.最大1ms错误

Hoo*_*och 10 c++ winapi multithreading sleep loops

我有运行循环的线程.我需要每5ms运行一次循环(1ms错误).我知道Sleep()函数不精确.

你有什么建议吗?

更新.我不能以其他方式做到这一点.在循环结束时,我需要某种睡眠.我也不想加载100%的CPU.

Arn*_*rno 12

不要在这里使用纺纱.使用标准方法可以达到所要求的分辨率准确度.

Sleep()当系统中断周期设置为以该高频率工作时,您可以使用大约1 ms的周期.查看Sleep()描述以获取详细信息,特别是具有获取和设置定时器分辨率多媒体定时器,获取有关如何设置系统中断周期的详细信息.当正确实施时,这种方法可获得的精度在几微秒范围内.

我怀疑你的循环也在做其他事情.因此,我怀疑你想要一个5毫秒的总时间,这将是Sleep()你在循环中花在其他东西上的剩余时间的总和.

对于这种情况,我建议使用Waitable Timer Objects,但这些计时器也依赖于多媒体计时器API的设置.我已经给了相关功能的概述更高的精确定时在这里.在这里可以找到更高深度的高精度定时.

为了获得更加准确和可靠的计时,您可能需要查看process priority classesthread priorities.关于Sleep()准确性的另一个答案就是这个.

但是,是否可以获得Sleep()精确的5 ms延迟取决于系统硬件.某些系统允许您以每秒1024个中断运行(由多媒体计时器API设置).这相当于0.9765625毫秒的周期.因此,您最接近的是4.8828125 ms.其他人允许更接近,特别是因为Windows 7在硬件提供上运行时的时间显着改善high resolution event timers.请参阅关于 MSDN和高精度事件计时器的定时器.

摘要:将多媒体计时器设置为以最大频率运行并使用等待计时器.


Shi*_*hah 12

我一直在寻找适合实时应用的轻量级跨平台睡眠功能(即高分辨率/高精度和可靠性).以下是我的发现:

安排基础知识

放弃CPU然后取回它是昂贵的.根据这篇文章,Linux上的调度程序延迟可能在10到30毫秒之间.因此,如果您需要以高精度睡眠不到10毫秒,那么您需要使用特殊的操作系统特定API.通常的C++ 11 std :: this_thread :: sleep_for不是高分辨率睡眠.例如,在我的机器上,快速测试显示,当我要求它睡眠仅1ms时,它经常睡眠至少3ms.

Linux的

最流行的解决方案似乎是nanosleep()API.但是,如果您希望<2ms睡眠时的分辨率高于您需要使用sched_setscheduler调用来设置线程/进程以进行实时调度.如果你不是nanosleep()就像过时的usleep一样,分辨率约为10ms.另一种可能性是使用警报.

视窗

这里的解决方案是使用其他人建议的多媒体时间.如果你想在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;
}
Run Code Online (Sandbox Code Playgroud)

跨平台代码

这是为Linux,Windows和Apple平台实现睡眠的time_util.cc.但是请注意,它没有像我上面提到的那样使用sched_setscheduler设置实时模式,所以如果你想使用<2ms,那么你需要另外做的事情.您可以做的另一项改进是,如果您在某个循环中调用sleep,则应避免一遍又一遍地调用CreateWaitableTimer for 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
Run Code Online (Sandbox Code Playgroud)

还可以在此处找到另一个更完整的跨平台代码.

另一个快速解决方

您可能已经注意到,上面的代码不再是非常轻量级的.它需要包括Windows标题,如果您正在开发仅标题库,那么可能不太理想.如果您需要睡眠时间少于2毫秒并且您不是非常热衷于使用操作系统代码,那么您可以使用以下简单的解决方案,这是跨平台的,并且在我的测试中运行良好.请记住,您现在没有使用经过大量优化的操作系统代码,这可能会更好地节省电源和管理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);
    }
}
Run Code Online (Sandbox Code Playgroud)

相关问题


Mar*_*ius 5

根据问题标签,我想您在Windows上。看看Multimedia Timers,他们在1ms以下发布精度。另一个选择是使用自旋锁,但是这基本上将使CPU核心保持最大使用率。

  • 事实上,他们并没有宣传低于 1 毫秒的精度。您必须查询支持的周期范围,然后使用 timeBeginPeriod 来处理该范围内的某些内容。由于 timeBeginPeriod 的值以毫秒为单位,因此您似乎不可能做得比 1 毫秒更好。哦,加速系统接近 timeBeginPeriod 会对系统性能和功耗产生负面影响,因此一旦不再需要此精度,请务必立即调用 timeEndPeriod。 (3认同)
  • @AdrianMcCarthy:除了[他们自己的关于“等待函数和超时间隔”的文档](https://learn.microsoft.com/en-us/windows/desktop/Sync/wait-functions#wait-functions-and- time-out-intervals) 指出“如果您调用 `timeBeginPeriod`,请在应用程序的早期调用它一次,并确保在应用程序的最后调用 `timeEndPeriod` 函数”,因为“频繁的调用会显着影响系统时钟、系统电源使用和调度程序”。因此,如果您多次调用都依赖此精度,则不应在每次调用之前和之后进行调整。 (2认同)
  • 鉴于“timeBeginPeriod”和“timeEndPeriod”函数似乎会修改操作系统全局状态(不仅仅是您自己的进程),并且文档似乎暗示与“timeEndPeriod”不匹配的“timeBeginPeriod”是“即使通过进程死亡也无法修复,似乎*真的*很容易(例如在调整时钟时出现段错误或以其他方式硬终止进程)意外地导致系统时钟永久处于次优状态(或至少在重新启动之前) 。对于任何依靠电池运行的东西来说都非常糟糕,因为增加的电量使用会带来伤害。总的来说似乎不是一个好主意。 (2认同)