静态变量和线程局部存储

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影响的人道歉。我要感谢所有报道过的人。

以上是关于静态变量和线程局部存储的主要内容,如果未能解决你的问题,请参考以下文章

线程局部存储主要用来干啥的?

多线程

C++11:在多线程程序中使用局部静态变量导致 coredump

自动存储,静态存储和动态存储

全局变量 静态变量 局部变量 啥时候创建 啥时候撤销

java 局部静态变量在多线程环境下是不是有线程安全问题??