c++ - 静态变量和线程局部存储

标签 c++ multithreading static

背景:

我发现了一些与跨多个线程的静态内存初始化相关的有趣边缘情况。具体来说,我正在使用 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/

    相关文章:

    c++ - 如何声明指针成员禁止删除它

    c++ - 为什么在第三个文件之前写入两个文件,即使指令是每次迭代都执行一个文件?

    java - android主类中函数的并行执行

    java - Java中多线程无法画图

    c++ - 在类定义之外使用 enable_if 实现函数

    c++ - 如何检查 Windows 可执行文件是否在 session 0 进程中运行?

    c# - 通过 ThreadLocal 访问 RabbitMQ IModel 是否有任何影响或陷阱?

    java - 当我们创建多个对象时,静态 block 会发生什么?

    C++ 静态链接 curl

    c++ - 访问静态和非静态的模板构造函数和函数