背景:
我发现了一些与跨多个线程的静态内存初始化相关的有趣边缘情况。具体来说,我正在使用 Howard Hinnant 的 TZ 库,它在许多不同的线程中对我的其余代码运行良好。
现在,我正在开发一个依赖于另一个线程和条件变量的日志记录类。不幸的是,当我尝试格式化计时表时 time_point
使用 date::make_zoned(data::locate_zone("UTC"), tp)
图书馆崩溃了。通过挖掘 tz.cpp
,我发现内部返回的时区数据库正在评估 NULL
.这一切都来自以下片段:
tzdb_list&
get_tzdb_list()
{
static tzdb_list tz_db = create_tzdb();
return tz_db;
}
可以看出,数据库列表是静态存储的。使用一些 printf() 和使用 GDB 一段时间后,我可以看到主线程的多次调用返回了相同的数据库,但返回
NULL
从我的记录器线程调用时。但是,如果我更改了
tzdb_list
的声明到:static thread_local tzdb_list tz_db = create_tzdb();
一切都按预期工作。这并不奇怪,因为
thread_local
将导致每个线程执行创建 tzdb_list
的独立实例的繁重工作。 .显然,这很浪费内存,而且很容易在以后出现问题。因此,我真的不认为这是一个可行的解决方案。问题:
NULL
指针)。 NULL
相比)? thread_local
内置于库中,我在可寻址区域的两端获得了截然不同的内存位置;为什么? 我怀疑这与分配线程内存与主进程内存的位置有关,但不知道线程分配区域的确切细节。 引用:
我的日志记录线程是用以下方法创建的:
outputThread = std::thread(Logger::outputHandler, &outputQueue);
而库的实际输出处理程序/调用(
LogMessage
只是 std::tuple
的类型定义):void Logger::outputHandler(LogQueue *queue)
{
LogMessage entry;
std::stringstream ss;
while (1)
{
queue->pop(entry); // Blocks on a condition variable
ss << date::make_zoned(date::locate_zone("UTC"), std::get<0>(entry))
<< ":" << levelId[std::get<1>(entry)
<< ":" << std::get<3>(entry) << std::endl;
// Printing stuff
ss.str("");
ss.clear();
}
}
可应要求提供其他代码和输出示例。
编辑 1
这绝对是我的代码中的一个问题。当我把所有东西都去掉时,我的记录器按预期工作。令我感到奇怪的是,我在完整应用程序中的测试用例只是在 main 中打印了两次,然后在手动退出之前调用了记录器。应用程序初始化的其余部分均未运行,但此时我正在链接所有支持库(Microsoft CPP REST SDK、C++ 的 MySQL 连接器和 Howard 的日期库(静态))。
我很容易看出有什么东西会占用这个内存,但是,即使在我的应用程序中“满”的情况下,我也不知道为什么主线程上的打印会起作用,但调用记录器的下一行会失败。如果某些东西在 init 中横盘整理,我希望所有调用都会中断。
我还注意到,如果我将记录器设为静态,问题就会消失。当然,这会改变内存布局,因此不排除堆/堆栈粉碎。我觉得有趣的是,我可以在
main()
的开头全局或在堆栈上声明记录器。并且两者都会以相同的方式出现段错误。但是,如果我将记录器声明为静态,则全局声明和基于堆栈的声明都有效。仍在尝试创建一个最小的测试用例来重现这一点。
我已经联系了
-lpthread
;自从这个应用程序诞生以来,已经差不多了。操作系统是在 Intel Xeon 上运行的 Fedora 27 x86_64。编译器:
$ g++ --version
g++ (GCC) 7.3.1 20180130 (Red Hat 7.3.1-2)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
最佳答案
看来这个问题是由 bug in tz.cpp which has since been fixed 引起的。 .
错误是有一个命名空间范围变量,其初始化不能保证以正确的顺序进行。这是通过将该变量转换为函数本地静态以确保正确的初始化顺序来解决的。
我向所有可能受到此错误影响的人道歉。我要感谢所有报告此事的人。
关于c++ - 静态变量和线程局部存储,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48980044/