我发现了一些有趣的边缘情况,涉及跨多个线程的静态内存初始化.具体来说,我使用的是Howard Hinnant的TZ库,它在许多不同的线程中对我的其余代码工作正常.
现在,我正在开发一个依赖于另一个线程和条件变量的日志类.不幸的是,当我尝试使用date :: make_zoned(data :: locate_zone(“UTC”),tp)格式化chrono time_point时,库崩溃了.在挖掘tz.cpp后,我发现内部返回的时区数据库正在评估为NULL.这一切都来自以下代码段:
tzdb_list& get_tzdb_list() { static tzdb_list tz_db = create_tzdb(); return tz_db; }
可以看出,数据库列表是静态存储的.使用一些printf()和一些时间使用GDB我可以看到从主线程多次调用返回相同的db,但是从我的记录器线程调用时返回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的typedef):
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中打印两次,在手动退出之前调用logger.其余的应用程序初始化都没有运行,但我在此时链接所有支持库(Microsoft CPP REST SDK,MysqL Connector for C和Howard的日期库(静态)).
我很容易看到有什么东西可以踩踏这个内存但是,即使在我的应用程序中的“完整”情况下,我也不知道为什么主线程上的打印会起作用,但下一行调用记录器会失败.如果在初始阶段横向发生某些事情,我希望所有的电话都能打破.
我还注意到,如果我使记录器保持静态,问题就会消失.当然,这会改变内存布局,因此不排除堆/堆栈粉碎.我觉得有趣的是我可以在main()的开头全局或堆栈上声明记录器,并且两者都会以相同的方式进行段错误.但是,如果我将logger声明为static,则全局和基于堆栈的声明都可以正常工作.
仍然试图创建一个再现这个的最小测试用例.
我已经用-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影响的人道歉.我要感谢所有报道过的人.