【问题标题】:Boost.Thread wakes up too late in 1.58Boost.Thread 在 1.58 中唤醒太晚了
【发布时间】:2015-08-03 14:25:24
【问题描述】:

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

使用 Boost 1.55,我能够在我的容差范围内(+/-100 毫秒)以极高的可靠性击中窗户。迁移到 Boost 1.58 后,我永远无法打开这些窗口。将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;
}

以下是引用 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

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

【问题讨论】:

  • 在大多数平台上,任何类型的线程睡眠功能都是“尽力而为”的交易;但如果它以前对你有用,它现在应该仍然有效......
  • 我同意,很明显 std::thread 正在提供“尽力而为”的服务,因为它只精确到 +/- 30 毫秒(不计算测量计算所需的时间)。但是, boost::this_thread::sleep_for 提供的效果比“尽力而为”要差得多——它似乎将我的值四舍五入到 30 秒。
  • 顺便说一句,在 Windows 8 上,您会再次看到与 Windows 7 大不相同的结果。我认为 Windows 10 会再次不同。

标签: c++ boost boost-thread


【解决方案1】:

从 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 的睡眠时间如此不准确。

【讨论】:

  • 这似乎是个奇怪的选择。
  • @DavidSchwartz:您认为合并相似的计时器是奇数还是 5%?
  • 5%。如果我在下午 3 点开会,迟到几分钟对我来说可能很好,但假设如果会议在几周后开会,迟到几个小时也没关系……这太疯狂了.这应该是一个通用功能。 (5% 的睡眠,但不少于 32 毫秒或超过 100 毫秒可能是一个合理的设计选择。)
  • @DavidSchwartz:你的逻辑有问题。 5% 的可容忍延迟即使长达数周也是无关紧要的,因为真正的计时器截止日期已正确发送到 Windows,并且 Windows 仅被允许将计时器触发最多延迟数周。实际上永远不会,因为下一个系统中断将触发所有到期的计时器,并且没有任何 Windows 系统会一次中断超过几秒钟,最多一分钟。
  • @NiallDouglas 你的逻辑有问题。一个参数碰巧是无关紧要的,因为即使你要求它做错事,一个实现碰巧做了正确的事情,但这并不意味着要求错误的事情是可以接受的。这就是你最终得到的代码与新版本的操作系统或库的冲突的方式。
【解决方案2】:

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

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

此更改修复的一个错误是系统睡眠问题。以前的实现可能会被系统睡眠弄糊涂,因为定时等待永远不会唤醒(好吧,在 29 天内它们会唤醒)。这个实现正确地处理了系统睡眠,并且希望现在已经成为过去。

最后,我个人认为,定时等待需要在 STL 中提供硬/软保证。然而,这是一个相当大的变化。即使实现了,除了硬实时操作系统之外,定时等待的硬度只能是尽力而为。这就是为什么它们首先被排除在 C++ 标准之外的原因,因为 C++ 11 在移动设备功耗被认为足以修改 API 之前就已经完成。

尼尔

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-17
      • 1970-01-01
      • 2013-05-29
      相关资源
      最近更新 更多