C++ 线程在1.58中醒来太晚

C++ 线程在1.58中醒来太晚,c++,boost,boost-thread,C++,Boost,Boost Thread,我有一个应用程序需要在某些窗口内工作(在本例中,所有窗口都相隔30秒)。当时间不在一个窗口内时,计算到下一个窗口中间的时间,并且线程在该时间内休眠(毫秒,使用boost::this\u thread::sleep\u for) 使用Boost 1.55,我能够以极高的可靠性在公差(+/-100ms)范围内敲打窗口。在迁移到Boost 1.58时,我永远无法打开这些窗口。将的boost::this\u-thread::sleep\u替换为的std::this\u-thread::sleep\u修复

我有一个应用程序需要在某些窗口内工作(在本例中,所有窗口都相隔30秒)。当时间不在一个窗口内时,计算到下一个窗口中间的时间,并且线程在该时间内休眠(毫秒,使用
boost::this\u thread::sleep\u for

使用Boost 1.55,我能够以极高的可靠性在公差(+/-100ms)范围内敲打窗口。在迁移到Boost 1.58时,我永远无法打开这些窗口。将的
boost::this\u-thread::sleep\u替换为
std::this\u-thread::sleep\u修复了该问题;但是,我需要
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;
}

我希望
std::thread
boost::thread
睡眠时间相同;然而,当要求睡眠29.1-29.9秒时,
boost::thread
似乎想要睡眠约30秒。我是否误用了
boost::thread
接口,或者这是从1.55开始引入的错误?

从Windows上的boost 1.58开始,
sleep\u 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;
            }
        }
    }
//优先使用聚合定时器,以提高功耗和定时器精度
如果(!target_time.is_sentinel())
{
详细信息::超时::剩余时间常量时间左=目标时间。剩余毫秒();
timer_handle=CreateWaitableTimer(NULL、false、NULL);
如果(计时器句柄!=0)
{
ULONG tolerable=32;//经验测试表明Windows在允许时忽略了这一点)//5%
容许时间=剩余时间。毫秒/20;
大整数到期时间=获取到期时间(目标时间);
bool const set_time_successed=detail_uu::SetWaitableTimerEx()(计时器句柄和到期时间,0,0,0,&detail_u::默认原因上下文,可容忍)!=0;
如果(设置时间成功)
{
超时\索引=句柄\计数;
句柄[handle_count++]=计时器句柄;
}
}
}

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

我就是负责对boost.Thread进行上述更改的人。1.58中的这一变化是在与Boost社区和微软进行了一段时间的协商后设计的,它可能会极大地提高移动设备的电池寿命。C++标准不保证任何定时等待实际上等待,或等待正确的周期,或任何接近正确的周期。因此,任何假定定时等待有效或准确的代码都是错误的。未来的Microsoft STL可能会对Boost.Thread进行类似的更改,因此STL的行为将与Boost.Thread相同。我可以补充一点,在任何非实时操作系统上,任何定时等待都是不可预测的,任何等待都可能比请求的时间晚很多。因此,社区认为这一变化有助于暴露STL的错误用法

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

此更改修复的一个错误是系统睡眠问题。以前的实现可能会被系统睡眠弄糊涂,因为定时等待永远不会唤醒(好吧,在29天内就会唤醒)。这个实现正确地处理了系统休眠和使用Boost的随机代码挂起。由系统休眠引起的线程有望成为过去

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

如果我需要睡眠的可中断性,我会将此代码用作解决方法:

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

在大多数平台上,任何类型的线程睡眠功能都是“尽力而为”的;但是,如果它以前对您有效,现在应该仍然有效……我同意,而且很明显,std::thread提供了“尽力而为”的服务,因为它只精确到+/-30ms(不计算测量计算所需的时间)。然而,boost::this_thread::sleep_for提供的效果比“尽力而为”差得多——它似乎将我的值四舍五入到30秒。顺便说一句,在Windows 8上,您将再次看到与Windows 7截然不同的结果。我假设Windows 10将再次不同。这似乎是一个奇怪的选择。@戴维施瓦茨:你认为合并类似的定时器奇,或5%?5%。如果我下午3点开会,迟到几分钟可能没问题,但假设离开会还有几个星期,迟到几个小时也没问题。。。那太疯狂了。这应该是一个通用函数。(5%的睡眠时间,但不少于32毫秒也不超过100毫秒可能是合理的设计选择。)@DavidSchwartz:你的逻辑有问题。5%的可容忍延迟是无关紧要的,即使它长达数周,因为真正的
        ::Sleep(20);
        boost::this_thread::interruption_point();