我可以传递给std :: thread :: sleep_for()和sleep_until()的最大值是多少?

时间:2017-03-07 02:00:59

标签: c++ sleep chrono

永远沉睡的

This question有一个答案可以提到:

std::this_thread::sleep_until(
    std::chrono::time_point<std::chrono::system_clock>::max());

和此:

std::this_thread::sleep_for(
    std::chrono::system_clock::durat‌​ion::max());

在Visual C ++ 2017 RC上运行此代码实际上根本不会睡觉。我还没有查看sleep_until()案例,所以我不确定那里发生了什么。

sleep_for()案例中,给定的duration似乎会通过将其添加到system_clock::now()转换为绝对时间,然后转发到sleep_until()。问题是加法溢出,过去给了时间。

查看30.3.2中的C ++ 17草案,sleep_until()sleep_for()似乎都没有提到限制。 时序规范(30.2.4)中没有任何相关内容。至于duration::max(),它在 duration_values (20.17.4.3)中描述为:&#34;返回的值应大于zero()&#34 ;,这根本没有帮助。

老实说,我很惊讶sleep_for() system_clock::duration::max()失败,因为它是一个对我来说非常有意义的构造。

我可以传递给具有明确定义行为的那些函数的最高值是什么?

2 个答案:

答案 0 :(得分:3)

从技术上讲,std::chrono::system_clock::durat‌​ion::max()应该睡很长时间(比你或你的孙子孙女还活着的时间长)。标准强制执行。

但实际上,实现者仍在学习如何处理不同精度持续时间内chrono次转换引起的溢出。所以错误很常见。

睡觉9'000h(一年多一点)可能更实际。这不会导致溢出。对于您的应用来说,它肯定是“永远”的。

但是,请不要犹豫向您的供应商发送错误报告,抱怨std::chrono::system_clock::durat‌​ion::max()不起作用。这应该。让它正常工作真的很棘手。并且使它工作是不可移植的,所以要求你写一些包装器来做它是不合理的。

isanae下面的优秀评论的激励,请求参考:

30.3.3 [thread.thread.this] / p7描述sleep_for说:

  

效果:阻止调用线程执行rel_time指定的相对超时(30.2.4)。

30.2.4 [thread.req.timing]是线程支持库中所有时序要求的规范,说:

  

2实现从超时返回时必然有一些延迟。中断响应,函数返回和调度中的任何开销都会导致“实现质量”延迟,表示为持续时间D i 。理想情况下,此延迟将为零。此外,对处理器和存储器资源的任何争用都会引起“管理质量”延迟,表示为持续时间D m 。延迟持续时间可能因超时而异,但在所有情况下,更短的更好。

     

3名称以_for结尾的成员函数采用指定持续时间的参数。这些函数产生相对超时。实现应使用稳定时钟来测量这些函数的时间。 330 给定持续时间参数D t ,超时的实时持续时间为{{ 1}} <子>吨 D <子> I + D <子>米 + D

好的,现在我很开心,因为我们不是在讨论成员函数。我们讨论的是命名空间范围函数。这是一个缺陷。随意submit one

但规范没有给出溢出的优雅。规范(几乎)清楚地表明,在指定的延迟之后,实现才能返回。它后面的多少是模糊的,但明确表示它之前无法返回。

如果你“捣乱”STL并且他不合作,只要把他介绍给我,我们就会解决它。 :-)也许有一个我没有看到的标准错误,应该修复。如果是这样,我可以帮助您针对标准而不是针对VS提交错误。或者VS可能已经解决了这个问题,并且修复程序可以在升级中使用。

如果这是VS中的错误,请让STL知道我非常乐意协助解决它。在不同的平台上解决这个问题有不同的权衡。

目前,我不能发誓我自己的实现(libc ++)中没有这个类的错误。所以这里没有高马。对于std :: lib来说,这是一个艰难的选择。

<强>更新

我查看了libc ++ sleep_forsleep_until通过“长时间”睡眠(正如操作系统可以处理的那样)正确处理溢出。 sleep_for有溢出错误。

这是一个非常轻微测试的固定sleep_until

sleep_until

基本策略是使用template <class _Clock, class _Duration> void sleep_until(const chrono::time_point<_Clock, _Duration>& __t) { using namespace chrono; using __ldsec = duration<long double>; _LIBCPP_CONSTEXPR time_point<_Clock, __ldsec> _Max = time_point<_Clock, nanoseconds>::max(); time_point<_Clock, nanoseconds> __ns; if (__t < _Max) { __ns = time_point_cast<nanoseconds>(__t); if (__ns < __t) __ns += nanoseconds{1}; } else __ns = time_point<_Clock, nanoseconds>::max(); mutex __mut; condition_variable __cv; unique_lock<mutex> __lk(__mut); while (_Clock::now() < __ns) __cv.wait_until(__lk, __ns); } 表示进行溢出检查,该表示不仅具有非常大的最大可表示值,而且还使用饱和算法(具有无穷大)。如果输入值太大而无法操作系统处理,请将其截断为操作系统可以处理的内容。

在某些平台上,可能不希望采用浮点运算。有人可能会使用long double。或者在进行比较之前,有一个更复杂的技巧转换为输入的“最小公倍数”和本机持续时间。该转换只涉及除法(不是乘法),因此不能溢出。但是,它并不总能给出几乎相等的两个值的准确答案。但它应该适用于这个用例。

对于那些对后者(__int128_t)策略感兴趣的人,以下是如何计算该类型:

lcm

可以认为namespace detail { template <class Duration0, class ...Durations> struct lcm_type; template <class Duration> struct lcm_type<Duration> { using type = Duration; }; template <class Duration1, class Duration2> struct lcm_type<Duration1, Duration2> { template <class D> using invert = std::chrono::duration < typename D::rep, std::ratio_divide<std::ratio<1>, typename D::period> >; using type = invert<typename std::common_type<invert<Duration1>, invert<Duration2>>::type>; }; template <class Duration0, class Duration1, class Duration2, class ...Durations> struct lcm_type<Duration0, Duration1, Duration2, Durations...> { using type = typename lcm_type< typename lcm_type<Duration0, Duration1>::type, Duration2, Durations...>::type; }; } // namespace detail lcm_type<duration1, duration2>相反。前者找到转换为仅分割的持续时间。后者找到转换为仅相乘的持续时间。

答案 1 :(得分:2)

未指定,它将溢出

我与Visual C ++标准库开发人员之一Billy O&#Neal和libc ++的主要作者Howard Hinnant进行过讨论。我的结论是线程库中的_for_until系列将以未指定的方式溢出,您不应该尝试将大的值传递给它们。我不清楚标准是否在该主题上不明确。

问题

所有定时函数 1 采用durationtime_point。两者都由其基础类型(表示)和比率(期间)定义。这段时间也可以被视为&#34;单位&#34;,例如秒或纳秒。

有两个可能发生溢出的主要地方:

  1. 在特定于平台的通话之前,
  2. 在转换为特定于平台的类型
  3. 期间

    通话前

    在这种情况下可以避免溢出,就像霍华德在他的回答中提到的那样,但是实施者仍在学习如何处理不同精度持续时间内chrono转换引起的溢出。 。

    例如,

    Visual C ++ 2017通过将给定的持续时间添加到返回的当前时间,以sleep_for()实现sleep_until()system_clock::now()。如果持续时间太长,则会溢出。其他库,例如libstdc ++,似乎没有这个问题。

    系统调用

    一旦你足够深入,你就必须与你正在进行实际工作的平台进行互动。这是它变得混乱的地方。

    例如,在libstdc ++上,对sleep_for()的调用最终在nanosleep()timespec。这是它的简化版本:

    auto s = duration_cast<seconds>(time);
    auto ns = duration_cast<nanoseconds>(time - s);
    
    timespec ts = { s.count(), ns.count() };
    nanosleep(&ts, &ts);
    

    很容易溢出这个:你只需要传递一个超过LLONG_MAX秒的时间:

    std::this_thread::sleep_for(hours::max());
    

    这会使duration_cast溢出到seconds并将ts.tv_sec设置为-3600,因为nanosleep()在负值上失败,所以它根本不会睡觉。 sleep_until()尝试在循环中调用nanosleep()会变得更好,但它会一直失败,因此在等待期间需要100%的处理器。

    Visual C ++ 2017库中也发生了同样的事情。忽略sleep_for()中的溢出,因为它将持续时间添加到当前时间,它最终调用Sleep,它以毫秒为单位取无符号的32位值。

    即使它调用了更像NtWaitForSingleObject()(它可能在将来)的更灵活的东西,它仍然只是一个带符号的64位值,以100纳秒为增量并且仍然可以溢出。

    错误和限制

    我个人认为<chrono>库中的溢出是一个错误,例如Visual C ++在sleep_for()方面的sleep_until()实现。我认为,在调用特定于平台的函数之前,无论你给出什么样的价值,都应该在最终转换之前不受影响。

    一旦你到达那里,如果平台在你要求的时间内不支持睡眠,那么就没有真正的解决方案。由于<chrono>被禁止抛出异常,我接受而不是溢出是一种可能性。虽然这会变成未定义的行为,但我希望实现在处理溢出时会更加小心,例如libstdc ++处理EINVAL和在紧密循环中旋转的各种失败。

    Visual C ++

    我引用了我从Billy O&#;; Neal收到的电子邮件中的一些内容,因为它们增加了标准库开发人员的观点:

      
        

    你是说这个:

    this_thread::sleep_for(system_clock::durat‌ion::max());
    
             

    是标准的未定义行为吗?

      
         

    据我所知,是的。它是一种灰色区域 - 没有为这些函数指定最大允许范围,但考虑到它们接受任意time_point / duration的性质,这可能得到某些用户的支持 - 提供标准库不知道的bignum类型,基本上强制转换为某些基础time_point / duration类型。 <chrono>的设计将溢出作为非目标处理(例如,请参阅duration_cast,它完全禁止实施&#34;如同无穷大&#34;和类似)。< / p>      

    标准[...]没有给我们任何方式来报告在这里转换失败 - 行为实际上是未定义的。我们明确被禁止抛出异常,我们无法推断如果您超过LLONG_MAX会发生什么,因此我们唯一可能的回答是&#34;就像无限&#34;或直接转到std::terminate(),不要过去,不要收200美元。

         

    libstdc ++和libc ++是system_clock实际映射到平台所理解的东西的目标平台,其中Unix时间戳是土地的法则。我们没有针对这样的平台,并且有义务映射到/来自&#34; DWORD毫秒&#34;和/或FILETIME

         

    关于我唯一能想到的可能是这个事情的合理用例,就是要有某种哨兵价值,这意味着&#34; infinity,&#34;但是如果我们想去那里,标准应该引入一个命名常量并描述其行为。

         

    我宁愿解决你的直接问题(希望时间值成为无限的哨兵),而不是试图强制进行溢出检查。当您不了解所涉及的类型时,溢出检查可能会变得非常昂贵(复杂性和运行时间),但检查魔术常量(例如chrono::duration<rep, period>::max()chrono::time_point<clock, duration>::max())应该是便宜。

    看起来未来的更新(ABI不兼容)会对<thread>进行重大更改,因此它不再在sleep_for()中溢出,但它仍然受到Windows API的限制支持。像NtWaitForSingleObject()这样的东西确实支持64位值,但签名,因为它支持相对(负)和绝对(正)时间。

    1 &#34;定时函数&#34;,我的意思是 30.2.4 [thread.req.timing] 适用的任何函数,例如this_thread::sleep_for()this_thread::sleep_until(),以及timed_mutexrecursive_timed_mutexcondition_variable等内容