【问题标题】:What is the maximum value I can pass to std::thread::sleep_for() and sleep_until()?我可以传递给 std::thread::sleep_for() 和 sleep_until() 的最大值是多少?
【发布时间】:2017-03-07 02:00:59
【问题描述】:

This question on sleep forever 有一个答案提到了这一点:

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)中描述为:“返回的值应该比较大于zero()”,一点用处都没有。 p>

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

我可以传递给那些具有良好定义行为的函数的最高值是多少?

【问题讨论】:

    标签: c++ sleep chrono


    【解决方案1】:

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

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

    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 实现在从超时返回时必然会有一些延迟。中断响应、函数返回和调度中的任何开销都会导致“实现质量”延迟,表示为持续时间Di。理想情况下,此延迟为零。此外,任何对处理器和内存资源的争用都会导致“管理质量”延迟,表示为持续时间Dm。延迟持续时间可能因超时而异,但在所有情况下,越短越好。

    3 名称以_for 结尾的成员函数采用指定持续时间的参数。这些函数产生相对超时。实现应该使用一个稳定的时钟来测量这些函数的时间。330给定一个持续时间参数Dt,超时的实时持续时间是Dt + Di + Dm .

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

    但是规范没有提供溢出的宽限。规范(几乎)清楚地表明,直到指定的延迟之后,实现才能返回。之后多少含糊不清,但很清楚之前不能返回。

    如果您“错误”了 STL 而他不合作,只需将他介绍给我,我们会解决的。 :-) 也许有一个我没有看到的标准错误,应该修复。如果是这样,我可以帮助您根据标准而不是 VS 提交错误。或者,也许 VS 已经解决了这个问题,并且可以通过升级获得修复。

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

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

    更新

    我查看了 libc++ sleep_forsleep_untilsleep_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&lt;duration1, duration2&gt; 视为common_type&lt;duration1, duration2&gt; 的反面。前者找到一个转换为只划分的持续时间。后者会找到一个持续时间,该持续时间只会成倍增加。

    【讨论】:

    • 你有“标准强制执行”的参考吗?我找不到任何说明sleep_for(system_clock::duration::max()) 应该如何表现的东西。在我去 bug STL 之前,我宁愿从标准中得到一个引用:)
    【解决方案2】:

    未指定,会溢出

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

    问题

    所有定时函数1采用durationtime_point。两者均由其基础类型 (representation) 和比率 (period) 定义。周期也可以被认为是一个“单位”,例如秒或纳秒。

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

    1. 在特定于平台的调用之前,并且
    2. 在转换为特定于平台的类型期间

    通话前

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

    例如,Visual C++ 2017 通过将给定的持续时间添加到由 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() 这样更灵活的东西(将来可能会这样),它仍然只是一个以 100 纳秒为增量的有符号 64 位值,并且仍然可以溢出。

    错误和限制

    我个人认为&lt;chrono&gt; 库本身的溢出是一个错误,例如Visual C++ 对sleep_for() 的实现就sleep_until() 而言。我认为在调用特定于平台的函数之前,您给出的任何值都应该在最终转换之前保持不变。

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

    Visual C++

    我从比利·奥尼尔 (Billy O'Neal) 的电子邮件中引用了一些内容,因为它们添加了标准库开发人员的观点:

    你是说这个吗:

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

    是标准未定义的行为吗?

    据我所知,是的。这是一个灰色区域——实际上没有为这些函数指定最大允许范围,但考虑到它们接受任意time_point/duration 的性质,这可能由标准库中的某些用户提供的 bignum 类型支持没有知识,基本上强制转换为一些底层time_point/duration 类型。 &lt;chrono&gt; 的设计将处理溢出视为非目标(例如,参见 duration_cast,它完全禁止实现“好像无穷大”等类似内容)。

    标准 [...] 没有给我们任何方法来报告此处转换失败 - 行为实际上是未定义的。我们被明确禁止抛出异常,我们无法推断如果您超过LLONG_MAX 会发生什么,因此我们唯一可能的回应是“好像无穷大”或直接转到std::terminate(),不要通过 go,do不要收 200 美元。

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

    我能想到的唯一可能是这个东西的合理用例是有某种哨兵值,意思是“无穷大”,但如果我们想去那里,标准应该引入一个命名常量和描述其行为。

    我宁愿解决您的直接问题(希望时间值成为无穷大的哨兵),而不是尝试强制进行溢出检查。当您对所涉及的类型一无所知时,溢出检查可能会变得非常昂贵(在复杂性和运行时间方面),但检查魔术常数(例如 chrono::duration&lt;rep, period&gt;::max()chrono::time_point&lt;clock, duration&gt;::max())应该很便宜。

    看起来未来的更新(ABI 不兼容)将对&lt;thread&gt; 进行重大更改,因此它不会再在sleep_for() 中溢出,但它仍然受到 Windows API 支持的限制。 NtWaitForSingleObject() 之类的东西确实支持 64 位值,但 有符号,因为它同时支持相对(负)和绝对(正)时间。

    1 “定时函数”是指适用于 30.2.4 [thread.req.timing] 的任何函数,例如 this_thread::sleep_for()this_thread::sleep_until(),还有timed_mutexrecursive_timed_mutexcondition_variable等内容。

    【讨论】:

      猜你喜欢
      • 2020-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-16
      • 2019-11-19
      相关资源
      最近更新 更多