我可以传递给 std::thread::sleep_for() 和 sleep_until() 的最大值是多少?
What is the maximum value I can pass to std::thread::sleep_for() and sleep_until()?
This question on sleeper forever 有一个答案提到了这个:
std::this_thread::sleep_until(
std::chrono::time_point<std::chrono::system_clock>::max());
还有这个:
std::this_thread::sleep_for(
std::chrono::system_clock::duration::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) 中描述为:"The value returned shall compare greater than zero()
",完全没有帮助。
老实说,我很惊讶地看到 sleep_for()
在 system_clock::duration::max()
上失败了,因为它是一个对我来说非常有意义的结构。
我可以传递给那些具有明确行为的函数的最高值是多少?
从技术上讲 std::chrono::system_clock::duration::max()
应该睡很长时间(比你或你的孙子们活得更久)。标准强制执行了这一点。
但实际上,实施者仍在学习如何处理由 chrono
不同精度持续时间之间的转换引起的溢出。所以错误很常见。
睡 9'000h
(一年多一点)可能更实际。这不可能导致溢出。对于您的应用来说,它肯定是“永远”的。
但是,不要犹豫,向您的供应商发送错误报告,抱怨 std::chrono::system_clock::duration::max()
不起作用。这应该。让它正常工作很棘手。让它工作是不可移植的,所以要求你写一些包装器来做它是不合理的。
受到下面isanae的精彩评论的启发,该评论要求参考:
30.3.3 [thread.thread.this]/p7 描述 sleep_for
说:
Effects: Blocks the calling thread for the relative timeout (30.2.4) specified by rel_time
.
30.2.4 [thread.req.timing] 是线程支持库中所有时序要求的规范,说:
2 Implementations necessarily have some delay in returning from a timeout. Any overhead in interrupt response, function return, and scheduling induces a “quality of implementation” delay, expressed as duration D
i. Ideally, this delay would be zero. Further, any contention for processor and memory resources induces a “quality of management” delay, expressed as duration D
m. The delay durations may vary from timeout to timeout, but in all cases shorter is better.
3 The member functions whose names end in _for
take an argument that specifies a duration. These functions produce relative timeouts. Implementations should use a steady clock to measure time for these functions.330 Given a duration argument D
t, the real-time duration of the timeout is D
t + D
i + D
m
.
好的,现在我被逗乐了,因为我们不是在谈论成员函数。我们谈论的是命名空间作用域函数。这是一个缺陷。随意 submit one.
但是规范没有提供溢出的宽限期。规范(几乎)清楚地表明,实现不能 return 直到 在 指定的延迟之后。 多少之后是模糊的,但清楚的是它不能return之前。
如果您“窃听”STL 而他不合作,请将他转介给我,我们会解决的。 :-) 也许有一个我没有看到的标准错误,应该修复。如果是这样,我可以帮助您针对标准而不是针对 VS 提交错误。或者也许 VS 已经解决了这个问题,并且可以在升级中修复。
如果这是 VS 中的错误,请告知 STL,我非常乐意协助修复它。在不同的平台上解决这个问题有不同的权衡。
目前,我不能发誓在我自己的实现 (libc++) 中没有这个 class 的错误。所以这里没有高马。 std::lib 很难做到这一点。
更新
我看过 libc++ sleep_for
和 sleep_until
。 sleep_for
通过休眠“长时间”正确处理溢出(OS 可以处理)。 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
表示法进行溢出检查,该表示法不仅具有非常大的最大可表示值,而且还使用饱和算法(具有无穷大)。如果输入值太大 OS 无法处理,请将其截断为 OS 可以处理的值。
在某些平台上,可能不希望求助于浮点运算。人们可能会使用 __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>
是 common_type<duration1, duration2>
的对立面。前者找到转换为仅划分的持续时间。后者找到一个持续时间,转换为仅相乘。
未指定,会溢出
我与 Visual C++ 标准库开发人员之一的 Billy O'Neal 和 libc++ 的主要作者 Howard Hinnant 进行了讨论。我的结论是 threading 库中的 _for
和 _until
系列将以未指定的方式溢出 ,您不应该尝试将较大的值传递给它们。我不清楚该标准是否在该主题上未指定。
问题
所有定时函数1 采用 duration
或 time_point
。两者都由它们的基础类型 (representation) 和比率 (period) 定义。周期也可以被认为是一个“单位”,例如秒或纳秒。
溢出主要发生在两个地方:
- 在特定于平台的调用之前,并且
- 在转换为特定于平台的类型期间
通话前
在这种情况下可以避免溢出,就像霍华德在他的回答中提到的那样,但是“实现者仍在学习如何处理由 chrono
不同精度持续时间之间的转换引起的溢出”。
例如,Visual C++ 2017 通过将给定持续时间添加到返回的当前时间来根据 sleep_until()
实现 sleep_for()
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 位值,并且仍然可以溢出。
错误和限制
我个人认为 <chrono>
库本身的溢出是一个错误,例如 Visual C++ 在 sleep_until()
方面对 sleep_for()
的实现。我认为在调用特定于平台的函数之前,您提供的任何值都应该在最终转换之前保持不变。
不过,一旦您到达那里,如果平台不支持在您要求的时间内休眠,则没有真正的解决方案。由于 <chrono>
被禁止抛出异常,我接受溢出是可能的。虽然这会变成未定义的行为,但我希望实现能更小心地处理溢出,例如 libstdc++ 在处理 EINVAL 和在紧密循环中旋转的各种失败。
Visual C++
我从 Billy O'Neal 收到的电子邮件中引用了一些内容,因为它们增加了标准库开发人员的观点:
Are you saying that this:
this_thread::sleep_for(system_clock::duration::max());
is undefined behaviour by the standard?
据我所知,是的。这是一个灰色区域——没有真正为这些函数指定最大允许范围,但考虑到它们接受任意 time_point
/duration
的性质,这可能由某些用户提供的 bignum 类型支持标准库不知道,转换为某些基础 time_point
/duration
类型基本上是强制性的。 <chrono>
的设计将处理溢出视为非目标(参见 duration_cast
,例如,它完全禁止实现“好像无穷大”和类似的东西)。
标准 [...] 没有给我们任何方式来报告转换失败——行为实际上是未定义的。我们被明确禁止抛出异常,我们无法推理如果你超过 LLONG_MAX
会发生什么,所以我们唯一可能的反应是“好像无穷大”或直接转到 std::terminate()
,不要pass go,不收$200
libstdc++ 和 libc++ 的目标平台 system_clock
实际上映射到平台理解的东西,其中 Unix 时间戳是土地法则。我们不针对这样的平台,有义务映射to/from“DWORD
毫秒”and/orFILETIME
.
我唯一能想到的可能是这个东西的合理用例是有某种标记值,意思是“无限”,但如果我们想去那里,标准应该引入一个命名的常数并描述其行为。
我宁愿解决您的直接问题(希望时间值成为无穷大的哨兵),也不愿尝试强制进行溢出检查。当您对所涉及的类型一无所知时进行溢出检查可能会变得非常昂贵(在复杂性和 运行 时间上),但检查魔术常量(例如 chrono::duration<rep, period>::max()
或 chrono::time_point<clock, duration>::max()
)应该便宜。
看起来未来的更新(ABI 不兼容)会对 <thread>
进行重大更改,因此它不会再在 sleep_for()
中溢出,但它仍然受到 Windows API 支持。 NtWaitForSingleObject()
之类的东西确实支持 64 位值,但是 signed,因为它同时支持相对(负)和绝对(正)时间。
1 “定时函数”,我指的是 30.2.4 [thread.req.timing]适用,例如this_thread::sleep_for()
和this_thread::sleep_until()
,但也适用于timed_mutex
、recursive_timed_mutex
、condition_variable
等
This question on sleeper forever 有一个答案提到了这个:
std::this_thread::sleep_until(
std::chrono::time_point<std::chrono::system_clock>::max());
还有这个:
std::this_thread::sleep_for(
std::chrono::system_clock::duration::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) 中描述为:"The value returned shall compare greater than zero()
",完全没有帮助。
老实说,我很惊讶地看到 sleep_for()
在 system_clock::duration::max()
上失败了,因为它是一个对我来说非常有意义的结构。
我可以传递给那些具有明确行为的函数的最高值是多少?
从技术上讲 std::chrono::system_clock::duration::max()
应该睡很长时间(比你或你的孙子们活得更久)。标准强制执行了这一点。
但实际上,实施者仍在学习如何处理由 chrono
不同精度持续时间之间的转换引起的溢出。所以错误很常见。
睡 9'000h
(一年多一点)可能更实际。这不可能导致溢出。对于您的应用来说,它肯定是“永远”的。
但是,不要犹豫,向您的供应商发送错误报告,抱怨 std::chrono::system_clock::duration::max()
不起作用。这应该。让它正常工作很棘手。让它工作是不可移植的,所以要求你写一些包装器来做它是不合理的。
受到下面isanae的精彩评论的启发,该评论要求参考:
30.3.3 [thread.thread.this]/p7 描述 sleep_for
说:
Effects: Blocks the calling thread for the relative timeout (30.2.4) specified by
rel_time
.
30.2.4 [thread.req.timing] 是线程支持库中所有时序要求的规范,说:
2 Implementations necessarily have some delay in returning from a timeout. Any overhead in interrupt response, function return, and scheduling induces a “quality of implementation” delay, expressed as duration
D
i. Ideally, this delay would be zero. Further, any contention for processor and memory resources induces a “quality of management” delay, expressed as durationD
m. The delay durations may vary from timeout to timeout, but in all cases shorter is better.3 The member functions whose names end in
_for
take an argument that specifies a duration. These functions produce relative timeouts. Implementations should use a steady clock to measure time for these functions.330 Given a duration argumentD
t, the real-time duration of the timeout isD
t+ D
i+ D
m.
好的,现在我被逗乐了,因为我们不是在谈论成员函数。我们谈论的是命名空间作用域函数。这是一个缺陷。随意 submit one.
但是规范没有提供溢出的宽限期。规范(几乎)清楚地表明,实现不能 return 直到 在 指定的延迟之后。 多少之后是模糊的,但清楚的是它不能return之前。
如果您“窃听”STL 而他不合作,请将他转介给我,我们会解决的。 :-) 也许有一个我没有看到的标准错误,应该修复。如果是这样,我可以帮助您针对标准而不是针对 VS 提交错误。或者也许 VS 已经解决了这个问题,并且可以在升级中修复。
如果这是 VS 中的错误,请告知 STL,我非常乐意协助修复它。在不同的平台上解决这个问题有不同的权衡。
目前,我不能发誓在我自己的实现 (libc++) 中没有这个 class 的错误。所以这里没有高马。 std::lib 很难做到这一点。
更新
我看过 libc++ sleep_for
和 sleep_until
。 sleep_for
通过休眠“长时间”正确处理溢出(OS 可以处理)。 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
表示法进行溢出检查,该表示法不仅具有非常大的最大可表示值,而且还使用饱和算法(具有无穷大)。如果输入值太大 OS 无法处理,请将其截断为 OS 可以处理的值。
在某些平台上,可能不希望求助于浮点运算。人们可能会使用 __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>
是 common_type<duration1, duration2>
的对立面。前者找到转换为仅划分的持续时间。后者找到一个持续时间,转换为仅相乘。
未指定,会溢出
我与 Visual C++ 标准库开发人员之一的 Billy O'Neal 和 libc++ 的主要作者 Howard Hinnant 进行了讨论。我的结论是 threading 库中的 _for
和 _until
系列将以未指定的方式溢出 ,您不应该尝试将较大的值传递给它们。我不清楚该标准是否在该主题上未指定。
问题
所有定时函数1 采用 duration
或 time_point
。两者都由它们的基础类型 (representation) 和比率 (period) 定义。周期也可以被认为是一个“单位”,例如秒或纳秒。
溢出主要发生在两个地方:
- 在特定于平台的调用之前,并且
- 在转换为特定于平台的类型期间
通话前
在这种情况下可以避免溢出,就像霍华德在他的回答中提到的那样,但是“实现者仍在学习如何处理由 chrono
不同精度持续时间之间的转换引起的溢出”。
例如,Visual C++ 2017 通过将给定持续时间添加到返回的当前时间来根据 sleep_until()
实现 sleep_for()
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 位值,并且仍然可以溢出。
错误和限制
我个人认为 <chrono>
库本身的溢出是一个错误,例如 Visual C++ 在 sleep_until()
方面对 sleep_for()
的实现。我认为在调用特定于平台的函数之前,您提供的任何值都应该在最终转换之前保持不变。
不过,一旦您到达那里,如果平台不支持在您要求的时间内休眠,则没有真正的解决方案。由于 <chrono>
被禁止抛出异常,我接受溢出是可能的。虽然这会变成未定义的行为,但我希望实现能更小心地处理溢出,例如 libstdc++ 在处理 EINVAL 和在紧密循环中旋转的各种失败。
Visual C++
我从 Billy O'Neal 收到的电子邮件中引用了一些内容,因为它们增加了标准库开发人员的观点:
Are you saying that this:
this_thread::sleep_for(system_clock::duration::max());
is undefined behaviour by the standard?
据我所知,是的。这是一个灰色区域——没有真正为这些函数指定最大允许范围,但考虑到它们接受任意
time_point
/duration
的性质,这可能由某些用户提供的 bignum 类型支持标准库不知道,转换为某些基础time_point
/duration
类型基本上是强制性的。<chrono>
的设计将处理溢出视为非目标(参见duration_cast
,例如,它完全禁止实现“好像无穷大”和类似的东西)。标准 [...] 没有给我们任何方式来报告转换失败——行为实际上是未定义的。我们被明确禁止抛出异常,我们无法推理如果你超过
LLONG_MAX
会发生什么,所以我们唯一可能的反应是“好像无穷大”或直接转到std::terminate()
,不要pass go,不收$200libstdc++ 和 libc++ 的目标平台
system_clock
实际上映射到平台理解的东西,其中 Unix 时间戳是土地法则。我们不针对这样的平台,有义务映射to/from“DWORD
毫秒”and/orFILETIME
.我唯一能想到的可能是这个东西的合理用例是有某种标记值,意思是“无限”,但如果我们想去那里,标准应该引入一个命名的常数并描述其行为。
我宁愿解决您的直接问题(希望时间值成为无穷大的哨兵),也不愿尝试强制进行溢出检查。当您对所涉及的类型一无所知时进行溢出检查可能会变得非常昂贵(在复杂性和 运行 时间上),但检查魔术常量(例如
chrono::duration<rep, period>::max()
或chrono::time_point<clock, duration>::max()
)应该便宜。
看起来未来的更新(ABI 不兼容)会对 <thread>
进行重大更改,因此它不会再在 sleep_for()
中溢出,但它仍然受到 Windows API 支持。 NtWaitForSingleObject()
之类的东西确实支持 64 位值,但是 signed,因为它同时支持相对(负)和绝对(正)时间。
1 “定时函数”,我指的是 30.2.4 [thread.req.timing]适用,例如this_thread::sleep_for()
和this_thread::sleep_until()
,但也适用于timed_mutex
、recursive_timed_mutex
、condition_variable
等