简体   繁体   English

Boost.Thread在1.58中太晚醒来

[英]Boost.Thread wakes up too late in 1.58

I have an application that needs to do work within certain windows (in this case, the windows are all 30 seconds apart). 我有一个应用程序,需要在某些窗口内工作(在这种情况下,窗口相隔30秒)。 When the time is not within a window, the time until the middle of the next window is calculated, and the thread sleeps for that amount of time (in milliseconds, using boost::this_thread::sleep_for ). 当时间不在窗口内时,计算下一个窗口中间的时间,并且线程休眠该时间量(以毫秒为单位,使用boost::this_thread::sleep_for )。

Using Boost 1.55, I was able to hit the windows within my tolerance (+/-100ms) with extreme reliability. 使用Boost 1.55,我能够在极限可靠性的范围内(+/- 100ms)击中窗户。 Upon migration to Boost 1.58, I am never able to hit these windows. 在迁移到Boost 1.58后,我无法打到这些窗口。 Replacing the boost::this_thread::sleep_for with std::this_thread::sleep_for fixes the issue; std::this_thread::sleep_for替换boost::this_thread::sleep_for可以解决问题; however, I need the interruptible feature of boost::thread and the interruption point that boost::this_thread::sleep_for provides. 但是,我需要boost::thread的可中断功能以及boost::this_thread::sleep_for提供的中boost::this_thread::sleep_for

Here is some sample code illustrating the issue: 以下是一些说明问题的示例代码:

#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;
}

Here is the output when referencing Boost 1.58 as an include directory and running on my workstation (Windows 7 64-bit): 以下是将Boost 1.58作为包含目录引用并在我的工作站上运行时的输出(Windows 7 64位):

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

I would expect the std::thread and the boost::thread to sleep for the same amount of time; 我希望std::threadboost::thread能够在相同的时间内休眠; however, the boost::thread seems to want to sleep for ~30 seconds when asked to sleep for 29.1 - 29.9 seconds. 然而,当被要求睡眠29.1秒至29.9秒时, boost::thread似乎想要睡眠约30秒。 Am I misusing the boost::thread interface, or is this a bug that was introduced since 1.55? 我是否滥用了boost::thread接口,或者这是自1.55以来引入的错误?

I am the person who committed the above change to Boost.Thread. 我是将上述更改提交给Boost.Thread的人。 This change in 1.58 is by design after a period of consultation with the Boost community and Microsoft, and results in potentially enormous battery life improvements on mobile devices. 1.58的这一变化是在与Boost社区和微软进行了一段时间的协商之后设计的,并且导致移动设备上的电池寿命大大改善。 The C++ standard makes no guarantees whatsoever that any timed wait actually waits, or waits the correct period, or anything close to the correct period. C ++标准不保证任何定时等待实际等待,或等待正确的时间段,或任何接近正确的时间段。 Any code written to assume that timed waits work or are accurate is therefore buggy. 因此,为了假定定时等待工作或准确而编写的任何代码都是错误的。 A future Microsoft STL may make a similar change to Boost.Thread, and therefore the STL behaviour would be the same as Boost.Thread. 未来的Microsoft STL可能会对Boost.Thread进行类似的更改,因此STL行为与Boost.Thread相同。 I might add that on any non-realtime OS any timed wait is inherently unpredictable any may fire very considerably later than requested. 我可能会在任何非实时操作系统上添加它,任何定时等待本质上都是不可预测的,任何时候都可能比请求时间大得多。 This change was therefore thought by the community as helpful to expose buggy usage of the STL. 因此,社区认为这种变化有助于暴露STL的错误使用。

The change allows Windows to optionally fire timers late by a certain amount. 此更改允许Windows可选地延迟一定量的计时器。 It may not actually do so, and in fact simply tries to delay regular interrupts as part of a tickless kernel design on very recent editions of Windows. 它可能实际上并没有这样做,实际上只是尝试延迟常规中断,作为最新版Windows上无滴答内核设计的一部分。 Even if you specify a tolerance of weeks, as the correct deadline is always sent to Windows the next system interrupt to occur after the timer expiry will always fire the timer, so no timer will ever be late by more than a few seconds at most. 即使您指定了几周的容差,因为正确的截止日期始终发送到Windows,在计时器到期后发生的下一个系统中断将始终触发计时器,因此没有计时器最多会延迟超过几秒钟。

One bug fixed by this change was the problem of system sleep. 这种变化导致的一个错误就是系统睡眠问题。 The previous implementation could get confused by the system sleeping whereby timed waits would never wake (well, in 29 days they would). 以前的实现可能会被系统睡眠混淆,因为定时等待永远不会被唤醒(好吧,他们会在29天内)。 This implementation correctly deals with system sleeps, and random hangs of code using Boost.Thread caused by system sleeps hopefully is now a thing of the past. 这个实现正确地处理了系统休眠,并且使用由系统休眠引起的Boost.Thread的代码随机挂起现在已成为过去。

Finally, I personally think that timed waits need a hardness/softness guarantee in the STL. 最后,我个人认为定时等待需要STL中的硬度/柔软度保证。 That's a pretty big change however. 然而,这是一个非常大的变化。 And even if implemented, except on hard realtime OSs hardness of timed waits can only ever be best effort. 即使实施,除了在硬实时操作系统上,定时等待的硬度只能是最好的努力。 Which is why they were excluded from the C++ standard in the first place, as C++ 11 was finalised well before mobile device power consumption was considered important enough to modify APIs. 这就是为什么他们首先被排除在C ++标准之外,因为在移动设备功耗被认为足以修改API之前,C ++ 11已经完成。

Niall 尼尔

Starting in Boost 1.58 on Windows, sleep_for() leverages SetWaitableTimerEx() (instead of SetWaitableTimer() ) passing in a tolerance time to take advantage of coalescing timers. 从Windows上的Boost 1.58开始, sleep_for()利用SetWaitableTimerEx() (而不是SetWaitableTimer() )传递容差时间以利用合并计时器。

In libs/thread/src/win32/thread.cpp, the tolerance is 5% of the sleep time or 32 ms, whichever is larger: 在libs / thread / src / win32 / thread.cpp中,容差是睡眠时间的5%或32 ms,以较大者为准:

// 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;
            }
        }
    }

Since 5% of 29.1 seconds is 1.455 seconds, this explains why the sleep times using boost::sleep_for were so inaccurate. 由于29.1秒的5%是1.455秒,这就解释了为什么使用boost::sleep_for的睡眠时间是如此不准确。

I use this code as a workaround if I need the interruptibleness of sleep_for: 如果我需要sleep_for的可中断性,我会使用此代码作为解决方法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM