静态变量和线程
背景:
我发现了一些有趣的边缘案例,涉及多线程中的静态存储器初始化。 具体来说,我使用的是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
的声明tzdb_list
为:
static thread_local tzdb_list tz_db = create_tzdb();
一切都按预期工作。 这并不奇怪,因为thread_local
会导致每个线程完成创建tzdb_list
的独立实例的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
这在我的代码中绝对是一个问题。 当我把所有东西都拿出来时,我的记录器按预期工作。 对我来说奇怪的是,我的测试用例在完整的应用程序中只有两个主要的打印和在手动退出之前调用记录器。 没有其他的应用程序初始化运行,但我在那个时候连接到所有支持库(Microsoft CPP REST SDK,MySQL Connector for C ++和Howard的日期库(静态))。
我很容易看到有什么东西可以跺脚这个内存,但即使在我的应用程序中的“完整”情况下,我也不知道为什么主线程上的打印件可以工作,但下一行调用到记录器中会失败。 如果在初始化过程中出现了一些问题,我希望所有的呼叫都会中断。
我还注意到,如果我让记录器静态,问题就会消失。 当然,这改变了内存布局,所以它不排除堆/堆栈粉碎。 我觉得有趣的是,我可以在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.
看来这个问题是由tz.cpp中的一个bug所导致的,后者已经被修正了。
该错误是有一个命名空间范围变量,其初始化不能保证在正确的顺序。 通过将该变量转换为函数本地静态来确保正确的初始化顺序,从而解决了这个问题。
我向所有可能受此错误影响的人致歉。 并感谢所有曾经报道过的人。
链接地址: http://www.djcxy.com/p/41361.html