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

时间:2014-09-02 08:07:52

标签: c++ c++11 thread-safety chrono

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

为什么这么根本被打破或者我忽略了什么?

6 个答案:

答案 0 :(得分:17)

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

  

除了最小限度之外,本文不提供日历服务   映射到C&#39 {s} time_t

     
    

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

  
     

本文未提出通用物理量   库。

     
    

本文提出了一个坚实的基础,在未来,可以为一般的物理单位提供兼容的起点     图书馆。虽然这样的未来图书馆可能采取多种形式,     目前的提案远远没有实际存在     单位图书馆。该提案是具体时间的,并且将继续如此     由线程库的时间相关需求驱动。

  
     

该提案的主要目标是满足人们的需求   标准库线程API,易于使用,安全   使用,高效,灵活,足以不被淘汰10甚至   100年后的今天。此提案中包含的每个功能都在此处   出于特定原因,以实际使用案例为动机。事情   它属于&#34;酷&#34;或者#34;这听起来像它可能   是有用的&#34;,或&#34;非常有用,但不需要此接口&#34;没有   被包括在内。这些项目可能出现在其他提案中,并且   可能针对TR。

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

答案 1 :(得分:11)

localtimegmtime具有静态的内部存储,这意味着它们不是线程安全的(我们必须返回指向数据结构的指针,因此它必须动态分配,静态值或全局值 - 因为动态分配会泄漏内存,这不是一个合理的解决方案,这意味着它必须是全局变量或静态变量[理论上,可以在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库没有使用这些功能?你不得不问那些编写该规范的人 - 我希望它具有便携性和便利性,但我并不完全确定。

答案 2 :(得分:7)

std::localtimestd::gmtime没有线程安全替代方法,因为您没有提出并在整个标准化过程中编组它。其他人都没有。

chrono唯一的日历代码是包含现有time_t函数的代码。标准化或编写新的项目超出了chrono项目的范围。执行此类标准化需要更多时间,更多工作,并添加更多依赖项。简单地包装每个time_t函数很简单,依赖性很少,而且很快。

他们的工作范围很窄。他们成功地关注了他们所关注的事情。

我建议您开始使用<calendar>或加入此类工作,为std创建强大的日历API。祝你好运,加快速度!

答案 3 :(得分:3)

如果您愿意使用free, open-source 3rd party library,以下是以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() << " UTC\n";
}

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

对于现代的,线程安全的std::localtime替换,您需要这个密切相关的higher level timezone library,语法如下所示:

#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替代品。例如,您想要查看Julian日历中的当前日期吗?

#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() << " UTC\n";
    std::cout << gps_clock::now() << " GPS\n";
}

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”和“tz.h”库现在在C ++ 2a规范草案中,只有很小的改动,我们希望'a'为'0'。它们将位于标题<chrono>namespace std::chrono下方(并且不会有date namespace)。

答案 4 :(得分:2)

正如其他人所提到的,在任何可用的C ++标准中都没有线程安全便利和便携式时间格式化方法,但我发现有一些古老的预处理器技术可用(感谢Andrei Alexandrescu在CppCon 2015幻灯片17&amp; 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__宏,在这种情况下更好地确保我们不调用宏{{1在一行中两次。

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

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

使用示例:

std::tm

答案 5 :(得分:0)

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