为什么没有C++11线程安全替代std::localtime和std::gmtime

Why is there no C++11 threadsafe alternative to std::localtime and std::gmtime?

本文关键字:std localtime gmtime C++11 线程 安全 为什么      更新时间:2023-10-16

在C++11中,您仍然必须使用std::localtimestd::gmtime作为间接打印std::chrono::time_point。这些函数在C++11中引入的多线程环境中使用是不安全的,因为它们返回一个指向内部静态结构的指针。这尤其令人讨厌,因为C++11引入了方便的函数std::put_time,由于同样的原因,该函数几乎不可用。

为什么这一点如此根本性地被打破,或者我忽略了什么?

根据N2661,添加<chrono>:的论文

本文不提供日历服务,除了映射到C的CCD_ 6。

由于本文没有提出日期/时间库,也没有指定时期,因此也没有涉及闰秒。但是,日期/时间图书馆会发现这是一个很好的基础建筑

本文没有提出一个通用的物理量图书馆

本文提出了一个坚实的基础,在未来,可以为通用物理单元提供一个兼容的起点图书馆虽然这种未来的图书馆可能采取几种形式中的任何一种,目前的提议远远没有达到实际的效果单位图书馆。这项提议是有时间限制的,并将继续受线程库的时间相关需求的激励。

该提案的主要目标是满足以易于使用、安全的方式对API进行标准库线程处理使用,高效,灵活,不会过时10甚至100年后。此提案中包含的每个功能都在此处出于特定的原因,以实际用例为动机。事情这属于"酷"的范畴,或者"听起来可能是有用的",或"非常有用但此接口不需要"包括在内。此类项目可能出现在其他提案中,以及可能针对TR

请注意,<chrono>的主要目标是"满足标准库线程API的需求",这不需要日历服务。

CCD_ 8和CCD_,这意味着它们不是线程安全的(我们必须返回一个指向数据结构的指针,所以它要么是动态分配的,要么是静态值,要么是全局值——因为动态分配会泄漏内存,这不是一个合理的解决方案,这意味着它必须是全局或静态变量[理论上,可以在TLS中分配和存储,并以这种方式使其线程安全])。

大多数系统都有线程安全的替代方案,但它们不是标准库的一部分。例如,Linux/Posix有localtime_rgmtime_r,这两个参数会为结果增加一个参数。请参见示例http://pubs.opengroup.org/onlinepubs/7908799/xsh/gmtime.html

类似地,Microsoft库有gmtime_s,它也是可重入的,以类似的方式工作(将输出参数作为输入传入)。看见http://msdn.microsoft.com/en-us/library/3stkd9be.aspx

至于标准的C++11库为什么不使用这些函数呢?你必须询问编写该规范的人——我希望它具有可移植性和方便性,但我不完全确定。

std::localtimestd::gmtime没有线程安全的替代方案,因为您没有提出一个并在整个标准化过程中对其进行封送。其他人也没有。

chrono唯一的日历代码是包装现有time_t函数的代码。标准化或编写新的不属于chrono项目的范围。进行这样的标准化将需要更多的时间、更多的精力,并增加更多的依赖性。简单地包装每个time_t函数非常简单,几乎没有依赖关系,而且速度很快。

他们的努力范围很窄。他们在专注的领域取得了成功

我鼓励您开始研究<calendar>或加入这样的努力,为std创建一个强大的日历API。祝你好运,祝你好运!

如果您愿意使用免费、开源的第三方库,这里有一种在UTC中打印std::chrono::system_clock::time_point的方法:

#include "date.h"
#include <iostream>
int
main()
{
    using namespace date;
    using namespace std::chrono;
    std::cout << system_clock::now() << " UTCn";
}

这是使用现代C++语法的std::gmtime的线程安全替代方案。

对于现代的线程安全std::localtime替换,您需要这个密切相关的更高级别时区库,语法如下:

#include "tz.h"
#include <iostream>
int
main()
{
    using namespace date;
    using namespace std::chrono;
    std::cout << make_zoned(current_zone(), system_clock::now()) << "n";
}

这两者都将以system_clock支持的任何精度输出,例如:

2016-07-05 10:03:01.608080 EDT

(macOS上为微秒)

这些库远远超出了gmtimelocaltime的替代品。例如,您想查看儒略历中的当前日期吗?

#include "julian.h"
#include <iostream>
int
main()
{
    using namespace std::chrono;
    std::cout << julian::year_month_day(date::floor<date::days>(system_clock::now())) << "n";
}
2016-06-22

当前的GPS时间怎么样?

#include "tz.h"
#include <iostream>
int
main()
{
    using namespace date;
    std::cout << std::chrono::system_clock::now() << " UTCn";
    std::cout << gps_clock::now() << " GPSn";
}
2016-07-05 14:13:02.138091 UTC
2016-07-05 14:13:19.138524 GPS

https://github.com/HowardHinnant/date

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0355r0.html

更新

"date.h"answers"tz.h"库现在已经在C++2a规范草案中了,只做了很小的更改,我们希望"a"是"0"。它们将位于标头<chrono>namespace std::chrono之下(并且不会有date namespace)。

正如其他人所提到的,在任何可用的C++标准中都没有线程安全的便利性和可移植的时间格式化方法,但我发现有一些过时的预处理器技术是可用的(感谢Andrei Alexandrescu在CppCon 2015幻灯片17&18上的介绍):

std::mutex gmtime_call_mutex;
template< size_t For_Separating_Instantiations >
std::tm const * utc_impl( std::chrono::system_clock::time_point const & tp )
{
    thread_local static std::tm tm = {};
    std::time_t const time = std::chrono::system_clock::to_time_t( tp );
    {
        std::unique_lock< std::mutex > ul( gmtime_call_mutex );
        tm = *std::gmtime( &time );
    }
    return &tm;
}

#ifdef __COUNTER__
#define utc( arg ) utc_impl<__COUNTER__>( (arg) )
#else
#define utc( arg ) utc_impl<__LINE__>( (arg) )
#endif 

在这里,我们用size_t模板参数声明函数,并返回指向静态成员std::tm的指针。现在,每个具有不同模板参数的函数调用都会创建一个具有全新静态std::tm变量的新函数。如果定义了__COUNTER__宏,则每次使用时都应将其替换为递增的整数值,否则我们使用__LINE__宏,在这种情况下,最好确保不会在一行中调用宏utc两次。

全局gmtime_call_mutex在每个实例化中保护非线程安全的std::gmtime调用,至少在Linux中不应该是性能问题,因为获取锁首先是在spinlock周围运行的,在我们的情况下永远不应该以真正的线程锁结束。

thread_local确保使用utc调用运行相同代码的不同线程仍将使用不同的std::tm变量。

用法示例:

void work_with_range(
        std::chrono::system_clock::time_point from = {}
        , std::chrono::system_clock::time_point to = {}
        )
{
    std::cout << "Will work with range from "
         << ( from == decltype(from)()
              ? std::put_time( nullptr, "beginning" )
              : std::put_time( utc( from ), "%Y-%m-%d %H:%M:%S" )
            )
         << " to "
         << ( to == decltype(to)()
              ? std::put_time( nullptr, "end" )
              : std::put_time( utc( to ), "%Y-%m-%d %H:%M:%S" )
            )
         << "."
         << std::endl;
    // ...
}

Boost:不完全确定这是否是线程安全的,但似乎是这样:

#include "boost/date_time/posix_time/posix_time.hpp"
std::wstring stamp = boost::posix_time::to_iso_wstring(
    boost::posix_time::second_clock::local_time());
std::wstring stamp = boost::posix_time::to_iso_wstring(
    boost::posix_time::second_clock::universal_time());

请参阅https://www.boost.org/doc/libs/1_75_0/doc/html/date_time/examples.html