我可以传递给std::线程::Slear_for()和Slear_Until()的最大值是多少?
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)中描述为:"返回的值应大于zero()
",这没有任何帮助。
老实说,我很惊讶地看到sleep_for()
在system_clock::duration::max()
中失败了,因为这是一个对我来说非常有意义的构造。
我可以传递给行为定义明确的函数的最大值是多少?
解决方案
它未指定,将溢出
我与VisualC++标准库开发人员之一billy O‘Neal和libc++的主要作者Howard Hinnant进行了讨论。我的结论是,线程库中的_for
和_until
族将以未指定的方式溢出,您不应尝试向它们传递较大的值。我不清楚该标准是否在这个问题上规定得不够具体。
问题
所有计时函数1都接受aduration
或atime_point
。两者都由其基础类型(表示)和比率(句点)定义。也可以将周期视为单位,如秒或纳秒。
可能发生溢出的主要位置有两个:
- 在平台特定的调用之前,和
- 转换为平台特定类型期间
调用前
在这种情况下可以避免溢出,就像Howard在回答中提到的那样,但实现者仍在学习如何在不同精度的持续时间之间进行chrono
转换而导致的溢出。
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_for()
一样。我认为无论您给出什么值,在调用特定于平台的函数之前,都应该在最终转换之前保持不变。
<chrono>
被禁止抛出异常,所以我接受溢出的可能性。尽管这将成为未定义的行为,但我希望实现能够更小心地处理溢出,例如libstdc++在处理EINVAL和在紧密循环中旋转方面的各种失败。
Visual C++
我引用了我从比利・奥尼尔那里收到的电子邮件中的一些东西,因为它们增加了标准库开发人员的观点:
看起来未来的更新(ABI不兼容)将对你是说:
this_thread::sleep_for(system_clock::durat?ion::max());
标准中是否有未定义的行为?
据我所知,是的。这是一种灰色区域--实际上并没有为这些函数指定最大允许范围,但是考虑到它们接受任意
time_point
/duration
的本质,它可能得到一些用户提供的bignum类型的支持,而标准库对此一无所知,因此转换成一些底层的time_point
/duration
类型基本上是强制的。<chrono>
的设计将处理溢出视为非目标(例如,请参阅duration_cast
,它直接禁止实现,就好像无穷大和类似情况一样)。标准[...]没有给我们任何方式来报告这里没有转换--行为从字面上来说是未定义的。我们被明确禁止抛出异常,如果超过
Libstdc++和libc++的目标平台是实际映射到平台理解的东西的平台,其中Unix时间戳是国家法律。我们的目标不是这样的平台,我们有义务在LLONG_MAX
会发生什么,我们无法进行推理,因此我们唯一可能的回应是,就像无穷大一样,或者直接转到std::terminate()
,不通过GO,不收取200美元。DWORD
毫秒和/或FILETIME
之间进行映射。我能想到的唯一合理的用例可能是拥有某种类型的前端值,这意味着无限,但如果我们想要这样做,标准应该引入一个命名常量并描述它的行为。
我宁愿解决您的直接问题(想要一个时间值作为无穷大的前哨),而不是试图强制进行溢出检查。当您对所涉及的类型一无所知时,溢出检查可能会变得非常昂贵(在复杂性和运行时方面),但检查幻数常量(例如
chrono::duration<rep, period>::max()
或chrono::time_point<clock, duration>::max()
)应该很便宜。
<thread>
进行重大更改,因此它不会再在sleep_for()
中溢出,但它仍然受到Windows API支持的限制。类似NtWaitForSingleObject()
的代码确实支持64位值,但有符号,因为它同时支持相对(负)和绝对(正)时间。
1指的是30.2.4[thread.req.timing]适用的任何函数,例如this_thread::sleep_for()
和this_thread::sleep_until()
,但也包括timed_mutex
、recursive_timed_mutex
、condition_variable
等中的内容。
相关文章