• C/C++ 11 thread_local 注意事项


    thread_local 等价于 “static thread_local”,即局部线程存储(TLS)技术,注意:此(TLS)并非某 SSL/TLS 安全传输层协议,那个东西哈,乱入要有一个限度。

    先贴一段代码:

    1. class Foo {
    2. public:
    3. ~Foo() {
    4. printf("close\r\n");
    5. }
    6. void say() {
    7. printf("say\r\n");
    8. }
    9. };
    10. std::thread([] {
    11. thread_local Foo foo;
    12. foo.say();
    13. }).detach();

    此处以 Windows VC++ 为例:(GCC实际上也差不多)

    我们先看看:foo.say() 是如何被执行的。

    1. 01094A7C 8B 0D 70 B3 59 01 mov ecx,dword ptr [_tls_index (0159B370h)]
    2. 01094A82 64 8B 15 2C 00 00 00 mov edx,dword ptr fs:[2Ch]
    3. 01094A89 8B 0C 8A mov ecx,dword ptr [edx+ecx*4]
    4. 01094A8C 89 81 14 01 00 00 mov dword ptr [ecx+114h],eax
    5. 01094A92 68 18 E1 F5 00 push offset `::operator()'::`2'::`dynamic atexit destructor for 'foo'' (0F5E118h)
    6. 01094A97 E8 61 9D EC FF call ___tlregdtor (0F5E7FDh)
    7. 01094A9C 83 C4 04 add esp,4
    8. 284: foo.say();

    可以看到它依赖于操作系统提供 Thread Local Store (TLS)线程存储机制,Windows API提供以下几个API:

    TlsAlloc、TlsGetValue、TlsSetValue、TlsFree,分别对应几种不同的行为,每个线程在分配时操作系统内核都会为线程单独分配一个“TLS索引数组缓冲区”,注意这是有限大小,所以不可能允许开发人员无限制的分配大量的TLS变量。

    而在 C++11 上面,thread_local 语法糖是存在内存泄露问题的,但按照标准化套路实现不会存在这个问题,举例:

    我们通过 CreateThread 函数创建一个新的工作线程,并在该线程内部使用 thread_local 声明一个变量,那么就会产生泄露,因为没有地方调用 C/C++ 11 编译器内部实现的 “thread_local” 变量资源回收函数,而该函数是非公开的(编译器,编译期实现)。

    而C/C++11提供的线程变量资源回收的契机是在 “thread_start(std::thread)”线程执行完毕以后,清理这些由 thread_local 语法糖声明持有的TLS线程存储变量资源。

     一个全新的问题?如果C/C++11编译的 main 入口点执行函数(即STA线程模式下的主线程)执行完毕会不会执行 “TLS线程存储变量资源”呢?答案是可以的。

    现代的C/C++11编译器,main 程序入口点函数已经不再是真正意义上的系统执行PE文件的可执行程序入口点函数了,在进入用户 main 函数以前现代 C/C++ 编译器会执行大量的由编译器生成的 “代码”,一般为初始化 “static global、common global” 定义的C++结构或类的分配(自然会调用构造函数),同理在 main 函数结束以后会执行相应的析构行为,而由C++11提供的 thread_local 实现的线程存储变量,自然也在析构的范畴。

    所以:thread_local 语法糖实现的线程存储变量,不允许在 std::thread 标准库线程模板以外的方式创建的线程上面使用,这类情况则需要人工手动管理TLS线程存储(如果非要用)。

    补充:但是CreateThread创建的线程上面仍旧可以使用 thread_local 进行释放,这种需要捕获系统释放TLS变量的堆栈,自己在C++中声明由编译器实现的线程回收函数,只要调用协议没什么问题,那么就可以让C/C++程序连接器连接上该函数,在线程结束的时候手动去调这个函数就可以回收了,只是不是很建议这样操作了,类似的操作也有替换C++编译器默认实现的 new,这种呢,依赖于对运算符的重载来实现,但缺点是只能是当前编译工程代码内可以被连接上,如其它编译为DLL的没什么用。

  • 相关阅读:
    【10天Unity入门计划】界面介绍(2)-Games视图&Hierarchy&Project&Inspector
    golang 微服务的负载均衡
    零基础自学SQL课程 | SQL中的日期函数大全
    Linux驱动调试之段错误分析_根据pc值确定出错的代码位置
    Linux SocketCAN 编程(C++,启用多线程接收)
    C++面试八股文:std::array如何实现编译器排序?
    北京/上海/广州/深圳DAMA-CDGA/CDGP数据治理认证报名条件
    ArduinoUNO实战-第十一章-4位共阴数码管实验
    无胁科技-TVD每日漏洞情报-2022-7-27
    Debian 12安装Docker
  • 原文地址:https://blog.csdn.net/liulilittle/article/details/125995213