Boost.Thread 1.58 醒得太晚了

Boost.Thread wakes up too late in 1.58

我有一个应用程序需要在某些 windows 内完成工作(在这种情况下,windows 都相隔 30 秒)。当时间不在 window 内时,计算直到下一个 window 中间的时间,线程休眠该时间量(以毫秒为单位,使用 boost::this_thread::sleep_for)。

使用 Boost 1.55,我能够以极高的可靠性在我的容忍范围内(+/-100 毫秒)达到 windows。迁移到 Boost 1.58 后,我永远无法达到这些 windows。将 boost::this_thread::sleep_for 替换为 std::this_thread::sleep_for 可以解决问题;但是,我需要 boost::thread 的可中断特性和 boost::this_thread::sleep_for 提供的中断点。

下面是一些说明该问题的示例代码:

#include <boost/thread.hpp>
#include <boost/chrono.hpp>

#include <chrono>
#include <iostream>
#include <thread>

void boostThreadFunction ()
{
   std::cout << "Starting Boost thread" << std::endl;
   for (int i = 0; i < 10; ++i)
   {
      auto sleep_time = boost::chrono::milliseconds {29000 + 100 * i};
      auto mark = std::chrono::steady_clock::now ();
      boost::this_thread::sleep_for (sleep_time);
      auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
         std::chrono::steady_clock::now () - mark);
      std::cout << "Boost thread:" << std::endl;
      std::cout << "\tSupposed to sleep for:\t" << sleep_time.count () 
                << " ms" << std::endl;
      std::cout << "\tActually slept for:\t" << duration.count () 
                << " ms" << std::endl << std::endl;
   }
}

void stdThreadFunction ()
{
   std::cout << "Starting Std thread" << std::endl;
   for (int i = 0; i < 10; ++i)
   {
      auto sleep_time = std::chrono::milliseconds {29000 + 100 * i};
      auto mark = std::chrono::steady_clock::now ();
      std::this_thread::sleep_for (sleep_time);
      auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
         std::chrono::steady_clock::now () - mark);
      std::cout << "Std thread:" << std::endl;
      std::cout << "\tSupposed to sleep for:\t" << sleep_time.count () 
                << " ms" << std::endl;
      std::cout << "\tActually slept for:\t" << duration.count () 
                << " ms" << std::endl << std::endl;
   }
}

int main ()
{
   boost::thread boost_thread (&boostThreadFunction);
   std::this_thread::sleep_for (std::chrono::seconds (10));
   std::thread std_thread (&stdThreadFunction);
   boost_thread.join ();
   std_thread.join ();
   return 0;
}

这是在我的工作站(Windows 7 64 位)上引用 Boost 1.58 作为包含目录和 运行 时的输出:

Starting Boost thread
Starting Std thread
Boost thread:
        Supposed to sleep for:  29000 ms
        Actually slept for:     29690 ms

Std thread:
        Supposed to sleep for:  29000 ms
        Actually slept for:     29009 ms

Boost thread:
        Supposed to sleep for:  29100 ms
        Actually slept for:     29999 ms

Std thread:
        Supposed to sleep for:  29100 ms
        Actually slept for:     29111 ms

Boost thread:
        Supposed to sleep for:  29200 ms
        Actually slept for:     29990 ms

Std thread:
        Supposed to sleep for:  29200 ms
        Actually slept for:     29172 ms

Boost thread:
        Supposed to sleep for:  29300 ms
        Actually slept for:     30005 ms

Std thread:
        Supposed to sleep for:  29300 ms
        Actually slept for:     29339 ms

Boost thread:
        Supposed to sleep for:  29400 ms
        Actually slept for:     30003 ms

Std thread:
        Supposed to sleep for:  29400 ms
        Actually slept for:     29405 ms

Boost thread:
        Supposed to sleep for:  29500 ms
        Actually slept for:     29999 ms

Std thread:
        Supposed to sleep for:  29500 ms
        Actually slept for:     29472 ms

Boost thread:
        Supposed to sleep for:  29600 ms
        Actually slept for:     29999 ms

Std thread:
        Supposed to sleep for:  29600 ms
        Actually slept for:     29645 ms

Boost thread:
        Supposed to sleep for:  29700 ms
        Actually slept for:     29998 ms

Std thread:
        Supposed to sleep for:  29700 ms
        Actually slept for:     29706 ms

Boost thread:
        Supposed to sleep for:  29800 ms
        Actually slept for:     29998 ms

Std thread:
        Supposed to sleep for:  29800 ms
        Actually slept for:     29807 ms

Boost thread:
        Supposed to sleep for:  29900 ms
        Actually slept for:     30014 ms

Std thread:
        Supposed to sleep for:  29900 ms
        Actually slept for:     29915 ms

我希望 std::threadboost::thread 休眠相同的时间;然而,boost::thread 在被要求睡眠 29.1 - 29.9 秒时似乎想要睡眠 ~30 秒。我是在误用 ​​boost::thread 接口,还是这是自 1.55 以来引入的错误?

从 Windows 的 Boost 1.58 开始,sleep_for() 利用 SetWaitableTimerEx()(而不是 SetWaitableTimer())传递容差时间以利用合并计时器。

在libs/thread/src/win32/thread.cpp中,公差为休眠时间的5%或32毫秒,以较大者为准:

// Preferentially use coalescing timers for better power consumption and timer accuracy
    if(!target_time.is_sentinel())
    {
        detail::timeout::remaining_time const time_left=target_time.remaining_milliseconds();
        timer_handle=CreateWaitableTimer(NULL,false,NULL);
        if(timer_handle!=0)
        {
            ULONG tolerable=32; // Empirical testing shows Windows ignores this when <= 26
            if(time_left.milliseconds/20>tolerable)  // 5%
                tolerable=time_left.milliseconds/20;
            LARGE_INTEGER due_time=get_due_time(target_time);
            bool const set_time_succeeded=detail_::SetWaitableTimerEx()(timer_handle,&due_time,0,0,0,&detail_::default_reason_context,tolerable)!=0;
            if(set_time_succeeded)
            {
                timeout_index=handle_count;
                handles[handle_count++]=timer_handle;
            }
        }
    }

由于 29.1 秒的 5% 是 1.455 秒,这就解释了为什么使用 boost::sleep_for 的睡眠时间如此不准确。

我是对 Boost.Thread 进行上述更改的人。 1.58 中的这一变化是在与 Boost 社区和 Microsoft 协商一段时间后设计的,并且可能会显着改善移动设备的电池寿命。 C++ 标准不保证任何定时等待实际等待,或等待正确的时间段,或任何接近正确时间段的东西。因此,任何假设定时等待有效或准确的代码都是错误的。未来的 Microsoft STL 可能会对 Boost.Thread 进行类似的更改,因此 STL 行为将与 Boost.Thread 相同。我可能会补充说,在任何非实时 OS 上,任何定时等待本质上都是不可预测的,任何可能会比请求的时间晚得多。因此,社区认为此更改有助于揭露 STL 的错误使用。

此更改允许 Windows 选择性地延迟一定时间触发计时器。它实际上可能不会这样做,实际上只是试图延迟常规中断,作为最近版本 Windows 的无滴答内核设计的一部分。即使您指定了数周的容差,因为正确的截止日期总是发送到 Windows,定时器到期后发生的下一个系统中断将始终触发定时器,因此没有定时器会迟到超过几个最多秒。

此更改修复的一个错误是系统睡眠问题。以前的实现可能会因系统休眠而感到困惑,因为定时等待永远不会醒来(好吧,29 天后他们会)。此实现正确地处理了系统休眠,并且希望由系统休眠导致的使用 Boost.Thread 的代码随机挂起现在已成为过去。

最后,个人认为STL中的定时等待需要hardness/softness保证。然而,这是一个相当大的变化。即使实现了,除了硬实时 OS 定时等待的困难也只能是尽力而为。这就是为什么他们首先被排除在 C++ 标准之外,因为 C++ 11 在移动设备功耗被认为重要到足以修改 API 之前就已经完成了。

尼尔

如果我需要 sleep_for 的可中断性,我会使用此代码作为解决方法:

        ::Sleep(20);
        boost::this_thread::interruption_point();