在 C++20 中获取当前日期/时间是否线程安全?

Will getting the current date/time be thread-safe in C++20?

本文关键字:是否 线程 安全 时间 C++20 获取 当前日期      更新时间:2023-10-16

简短的问题

在 C++17 之前(包括 C++)之前,不提供线程安全的方式来获取当前时间或日期。这会在 C++20 中修复吗?

长问题

获取当前时间和日期的唯一可移植方法是使用 std::gmtime 或 std::localtime 函数。这些函数从 C 语言早期开始的残余,将自实现定义的纪元以来的给定时间转换为日历时间(例如,1515153600转换为 2018 年 1 月 5 日星期五 12:00:00 GMT)。但是,唯一的缺点是这些函数返回指向内部静态变量的指针,并且不是线程安全的。更糟糕的是,这个静态变量可能由所有相关函数共享,例如 std::gmtime、std::localtime 和 std::ctime,并且可能会在每次调用这些函数时被覆盖。因此,如果您正在使用线程并希望定期检查时间,则面临数据竞争和未定义行为的风险。

显然,现行标准在这方面被打破了。C++标准委员会是否努力解决这个问题,这有多大的可能性被纳入C++20?

Howard Hinnant的日期库即将推出C++20。它通过p0355r4提出,并于2017年11月批准用于C++20。线程安全吗?不幸的是,文件和提案似乎都不清楚这一点。但是,某些功能(如get_tzdb_list)被明确称为具有"线程安全"。你最好的选择是在gitter聊天中问Hinnant本人。但是,为什么没有 C++11 线程安全替代 std::localtime 和 std::gmtime 中的讨论?似乎表明它是线程安全的(即使从未明确说过)。正如Nicol Bolas指出的那样,你可以把它包装在互斥锁后面。

如果没有,您可以再次获得数据竞争,从而获得未定义的行为。如果你在编码工作分为团队的大型项目中编码,你必须不断提醒每个人,他们不应该(!)使用C++标准提供的函数,而是使用你自己的包装器(否则冒着未定义的行为)。

在浅层次上,这就是代码审查的目的。Facebook对于初级开发人员来说有这个问题,他们一遍又一遍地制造相同的错误。如果你的团队有"奇怪的反复出现的错误",你需要以某种方式解决它(比如向linter添加检查:Clang浮现在脑海中)。

在更直接的层面上,谷歌就是这方面的缩影。他们遇到的问题是使用旧的 COW 实现string并切换到基于 SSO 的string。但是,由于他们依赖于使用基于COW的string的第三方库,因此他们需要在代码库中同时支持两者。告诉开发人员使用Google包装器是徒劳的。开发人员提出的解决方案是使用inline namespaces的黑客攻击。有点极端,但如果你正在处理一个类似的大代码库,它可以解决问题。

用于获取 C++20 中的当前时间的线程安全代码

#include <chrono>
#include <format>
#include <iostream>
int
main()
{
using namespace std;
using namespace std::chrono;
auto now = system_clock::now();
cout << format("{:%F %T %Z}", now) << 'n';
cout << format("{:%F %T %Z}", zoned_time{current_zone(), now}) << 'n';
cout << format("{:%F %T %Z}", zoned_time{"Australia/Sydney", now}) << 'n';
}

示例输出:

2022-06-09 01:14:31.844518 UTC
2022-06-08 21:14:31.844518 EDT
2022-06-09 11:14:31.844518 AEST
此示例使用system_clock

获取 UTC 的当前时间,显示该时间,然后将该时间转换为计算机的当前本地时区和澳大利亚悉尼的当前时间。

所有这些都是线程安全的。 您可以在多个线程中执行完全相同的代码,并且不会有争用条件。

免责声明:在我撰写本文时,并非所有平台都实现了 C++20 的这一部分。 有关更多详细信息,请参阅您的 std::lib 的 C++20 状态页面。