繁忙的循环/旋转在Windows下有时需要太长时间

时间:2017-09-04 14:34:36

标签: c++ windows visual-studio-2013 real-time spinning

我正在使用Windows 7 PC以1kHz的速率输出电压。起初我只是用sleep_until(nextStartTime)结束了线程,但事实证明这是不可靠的,有时工作正常,有时甚至长达10ms。

我在这里找到了其他答案,说繁忙的循环可能更准确,但是出于某种原因我的有时也需要太长时间。

while (true) {
        doStuff();  //is quick enough
        logDelays();

        nextStartTime = chrono::high_resolution_clock::now() + chrono::milliseconds(1);

        spinStart = chrono::high_resolution_clock::now();

        while (chrono::duration_cast<chrono::microseconds>(nextStartTime - 
                         chrono::high_resolution_clock::now()).count() > 200) {
            spinCount++; //a volatile int
        }
        int spintime = chrono::duration_cast<chrono::microseconds>
                              (chrono::high_resolution_clock::now() - spinStart).count();

        cout << "Spin Time micros :" << spintime << endl;

        if (spinCount > 100000000) {
            cout << "reset spincount" << endl;
            spinCount = 0;
        }

}

我希望这可以解决我的问题,但它会产生输出:

  Spin Time micros :9999
  Spin Time micros :9999
  ...

过去5个小时我一直坚持这个问题,如果有人知道解决方案,我会非常感激。

2 个答案:

答案 0 :(得分:0)

在Windows上我不认为它可能会得到如此精确的计时,因为你无法在你想要的时候实际运行你的线程。即使CPU使用率低并且将线程设置为实时优先级,它仍然可以被中断(硬件中断,据我所知。从来没有完全调查,但即使是实时的简单while(true) ++i;类型循环我见过中断然后在CPU之间移动核心)。虽然对于实时线程的这种中断和切换非常快,但如果你试图在没有缓冲的情况下直接驱动信号,它仍然很重要。

相反,您确实想要读取和写入数字样本的缓冲区(因此,每个样本的1KHz为1ms)。您需要确保在最后一个缓冲区完成之前排队另一个缓冲区,这将限制它们的小小,但如果代码很简单,并且没有其他CPU争用单个样本缓冲区(1ms)甚至可能是实时优先级的1KHz是可能的,这是最差的1ms额外延迟超过&#34;立即&#34;但你必须测试。然后将其留给硬件及其驱动程序来处理精确的时序(例如,确保每个输出样本都是&#34;与供应商声称的准确度完全相同)。

这基本上意味着你的代码在最坏的情况下只需要精确到1ms,而不是试图说出比操作系统真正支持的更小的东西,例如微秒精度。

只要您能够在硬件用完前一个缓冲区之前排队新缓冲区,它就能够以所需的频率运行而不会出现问题(再次使用音频作为示例,而容忍的延迟通常是如果你使CPU过载,你仍然有时会听到一个应用程序没有及时排队新的原始音频的可能故障)。更高,因此缓冲区也是如此。

通过仔细的计时,您甚至可以通过等待处理并尽可能长时间地排队下一个样本(例如,如果您需要减少输入和输出之间的延迟)来降低到几分之一毫秒,但请记住你越靠近它,你提交它的风险就越晚。

答案 1 :(得分:0)

根据评论,这段代码正确等待:

auto start = std::chrono::high_resolution_clock::now();
const auto delay = std::chrono::milliseconds(1);
while (true) {
    doStuff();  //is quick enough
    logDelays();

    auto spinStart = std::chrono::high_resolution_clock::now();
    while (start > std::chrono::high_resolution_clock::now() + delay) {}
    int spintime = std::chrono::duration_cast<std::chrono::microseconds>
                          (std::chrono::high_resolution_clock::now() - spinStart).count();

    std::cout << "Spin Time micros :" << spintime << std::endl;
    start += delay;
}

重要的部分是忙碌等待while (start > std::chrono::high_resolution_clock::now() + delay) {}start += delay;,它们将确保等待delay个时间,即使在外部因素(Windows更新保持系统)忙着打扰它。如果循环花费的时间超过delay,循环将被执行而不等到它赶上(如果doStuff足够慢,则可能永远不会。)

请注意,错过更新(由于系统正忙)然后一次发送2来赶上可能不是处理这种情况的最佳方法。您可能需要检查doStuff内的当前时间,如果时间错误超过一些可接受的数量,则中止/重新启动传输。