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

Linux/crossplatform API for timezone rules? (replace locking localtime_r)

本文关键字:锁定 替换 localtime 规则 Linux API 跨平台 时区      更新时间:2023-10-16

我们有一些代码想要经常从多个线程调用localtime。(相关背景:它是一个服务器,你可以要求它提供本地时间作为字符串,它希望每秒能够处理 100K 的请求。

我们发现在 Ubuntu Linux 12.04 上,glibc 函数localtime_r("可重入本地时间")调用 __tz_convert ,它仍然需要全局锁定!

(另外,看起来 FreeBSD 在每次调用时都会localtime_r调用tzset, 因为他们偏执地认为程序可能已经做了一个setenv("TZ")和/或用户从现在到上次调用localtime_r之间下载了新版本的/etc/localtime。(这与这里描述的情况相反;似乎glibc在每次调用localtime时都会调用tzset,而不是localtime_r,只是为了混淆。

显然,这对性能来说是可怕的。出于我们的目的,我们希望在服务器开始运行时基本上"快照"当前时区的规则,然后永久使用该快照。因此,我们将继续遵守夏令时规则(因为何时切换到 DST 的规则将是快照的一部分),但我们永远不会返回磁盘、使用互斥锁或执行任何其他会导致线程阻塞的操作。(我们不尊重下载的 tzinfo 更新,也不尊重对/etc/localtime的更改;我们不希望服务器在运行时物理更改时区。

但是,我在网上找不到任何关于如何处理时区规则的信息——是否有使用它们的用户空间 API,或者我们是否会被迫重新实现几百行 glibc 代码来自己读取时区数据。

我们是否必须重新实现__tz_convert下游的所有内容——包括 tzfile_read ,因为它似乎没有暴露给用户?或者是否有一些POSIX接口和/或第三方库可用于处理时区规则?

(我看过 http://www.iana.org/time-zones/repository/tz-link.html 但我不确定它是否有帮助。

使用 https://github.com/google/cctz

它速度很快,应该使用非常简单的 API 完成您想要的一切。

特别是,对于等效于 localtime 的 cctz,请使用 cctz::BreakTime() 函数。例如,https://github.com/google/cctz/blob/master/examples/example3.cc

也许这个免费的开源时区库可以满足需求。

它有一个名为 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

有关更多详细信息,请参阅文档。