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

isa*_*nae 4 c++ sleep c++-chrono

这个关于永远睡觉的问题有一个提到这一点的答案:

std::this_thread::sleep_until(
    std::chrono::time_point<std::chrono::system_clock>::max());
Run Code Online (Sandbox Code Playgroud)

和这个:

std::this_thread::sleep_for(
    std::chrono::system_clock::durat??ion::max());
Run Code Online (Sandbox Code Playgroud)

在 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()”,这根本没有帮助。

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

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

How*_*ant 6

从技术上讲,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说:

效果:在由 指定的相对超时 (30.2.4) 内阻塞调用线程rel_time

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

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

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

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

但是规范没有提供溢出的恩典。规范(几乎)清楚地表明,在指定的延迟之后,实现才能返回。之后多少是模糊的,但明确它不能在之前返回。

如果你“bug”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);
}
Run Code Online (Sandbox Code Playgroud)

基本策略是使用一种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
Run Code Online (Sandbox Code Playgroud)

可以认为lcm_type<duration1, duration2>是 的反面common_type<duration1, duration2>。前者找到转换为仅除以的持续时间。后者找到转换为仅相乘的持续时间。