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::thread
和 boost::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();
我有一个应用程序需要在某些 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::thread
和 boost::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();