Linux / 跨平台的时区规则 API?(替代锁定 localtime_r)

7
我们有一些代码需要从多个线程中经常调用localtime函数。(相关背景:这是一个服务器,你可以向它请求本地时间作为字符串,并且它希望能够每秒处理数十万个请求。)
我们发现在Ubuntu Linux 12.04上,glibc库函数localtime_r ("可重入的本地时间")调用了__tz_convert函数,而__tz_convert函数仍然使用了全局锁! (此外,看起来FreeBSDlocaltime_r函数在每次调用时都调用tzset函数,因为他们担心程序可能已经执行了setenv("TZ")操作,或者用户在上次调用localtime_r函数之后下载了新版本的/etc/localtime文件。(这与这里描述的情况相反;似乎glibc在每次调用localtime函数时都会调用tzset函数,但不会在每次调用localtime_r函数时调用,这非常令人困惑。)
显然,这对性能来说是很糟糕的。为了我们的目的,我们希望在服务器开始运行时“快照”当前时区的规则,然后在以后的所有时间里都使用该快照。因此,我们将继续遵守夏令时规则(因为切换到夏令时的规则将成为快照的一部分),但我们将永远不会回到磁盘、锁定互斥锁或执行任何其他可能导致线程阻塞的操作。(我们可以不考虑下载的tzinfo更新以及/etc/localtime更改;我们不希望服务器在运行时物理更改时区。)
然而,我在网上找不到有关如何处理时区规则的任何信息 - 是否有一个用户空间API可用于处理它们,或者我们将被迫重新实现几百行glibc代码以自己读取时区数据。
我们必须重新实现__tz_convert函数及其下游的所有内容,包括tzfile_read函数吗?还是有一些POSIX接口和/或第三方库可用于处理时区规则?
我看过http://www.iana.org/time-zones/repository/tz-link.html,但不确定它是否有帮助。

tzset 中的全局锁 “对性能非常糟糕”这一点并不明显 - 你是否真的进行了基准测试?除非你做过基准测试,否则这似乎是一个俗语般的过早优化案例。据我所知,tzset 有一种快速路径,如果时区实际上没有改变,它基本上什么也不做。在实际应用中,我预计锁永远不会争用。 - user4815162342
每当两个线程同时调用__tz_convert时,锁就会被争夺;请检查代码。是的,我们之所以知道这个锁存在,是因为在性能测试中它成为了瓶颈(特定测试服务于大量本地时间请求)。 - Quuxplusone
1
你看过Boost或者ICU吗? - Matt Johnson-Pint
看起来Boost.Date_Time可以实现我们想要的功能,但是它在依赖方面似乎有些笨重。(我们目前不是“Boost商店”。)然而,如果没有更简单的解决方案... Boost绝对是一个可能性。 - Quuxplusone
我不是C++大师,但我知道Boost和ICU都有IANA TZDB实现。我也知道ICU被v8-i18n项目和其他一些项目使用,所以它可能比Boost更轻量级,但我不确定。也许其他更熟悉的人可以提供更好的答案。 - Matt Johnson-Pint
2个回答

3

2
通常,对于工具或库的链接,应该附带使用说明、关于链接资源如何适用于问题的具体解释或一些示例代码,如果可能的话,最好都包含。 - IKavanagh
谢谢Greg。那个库看起来很棒。你的库中也有一个全局锁,请参见https://github.com/google/cctz/blob/master/src/time_zone_impl.cc#L64。Cloudera在https://issues.cloudera.org/browse/IMPALA-3316的评论中发现了这个问题。已提交https://github.com/google/cctz/issues/23。 - Tagar

1
也许这个免费、开源的时区库适合您的需求。
它有一个名为LAZY_INIT的配置标志,完全在此处记录。默认情况下,它打开,并会在首次访问每个单独的时区时调用std::call_once。但是您可以进行编译:
-DLAZY_INIT=0

然后对 std::call_once 的调用消失了。每个时区都从磁盘中读取并在第一次访问时完全初始化(通过函数本地静态变量)。从那时起,事情就稳定了,无需锁定且不需要磁盘访问。自然地,这增加了前期的初始化时间,但减少了每个时区的“首次访问”时间。

此库需要 C++11/14,因此可能由于这个原因而不适合。它基于(并且大量使用)C++11 <chrono> 库。以下是打印当前本地时间的示例代码:

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    auto local = make_zoned(current_zone(), std::chrono::system_clock::now());
    std::cout << local << '\n';
}

这只是为我输出的:

2016-04-12 10:13:14.585945 EDT

该库是一个现代化、高性能的线程安全设计。它也非常灵活且有完整的文档支持。其功能远不止于简单替换 C 的 localtime。与 C API 不同的是,您可以指定任何 IANA 时区,例如:
    auto local = make_zoned("Europe/London", std::chrono::system_clock::now());

这是伦敦的当前时间:

2016-04-12 15:19:59.035533 BST

请注意,默认情况下,时间戳的精度与 std::chrono::system_clock 相同。如果您希望使用其他精度,可以轻松实现:
using namespace date;
using namespace std::chrono;
auto local = make_zoned("Europe/London", std::chrono::system_clock::now());
std::cout << format("%F %H:%M %Z", local) << '\n';

2016-04-12 15:22 BST

查看 文档 以获取更多详细信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接