静态变量和线程局部存储
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了静态变量和线程局部存储相关的知识,希望对你有一定的参考价值。
背景:
我发现了一些与多线程静态内存初始化有关的有趣边缘情况。具体来说,我使用的是Howard Hinnant的TZ库,它在许多不同的线程中对我的其余代码工作正常。
现在,我正在开发一个依赖于另一个线程和条件变量的日志类。不幸的是,当我尝试使用time_point
格式化chrono 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时间,我可以看到从主线程多次调用返回相同的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 in tz.cpp which has since been fixed引起的。
错误是有一个命名空间范围变量,其初始化不能以正确的顺序保证。通过将该变量转换为函数local-static来确定正确的初始化顺序,从而解决了这个问题。
我向所有可能受到这个bug影响的人道歉。我要感谢所有报道过的人。
以上是关于静态变量和线程局部存储的主要内容,如果未能解决你的问题,请参考以下文章