news 2026/2/23 5:39:30

跟我学C++中级篇—线程局部存储的底层分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
跟我学C++中级篇—线程局部存储的底层分析

一、线程数据控制

在实际的开发中,经常遇到各种情况的数据处理。最典型的就是开发者经常遇到的线程数据共享的情况,不管是利用互斥变量还是其它形式的同步机制,可以保证线程间数据交互的安全性。但有一种情况下,恰恰是需要各个线程需要自己处理自己的数据。对于开发者来说,最简单的方式就是定义一个数据然后与线程绑定。但这种方式有不少缺点,除了编程的复杂性外,也需要开发者显式的控制相关线程与数据的绑定依赖关系。这在线程的中引入了不必要的麻烦。
在Windows平台和Posix库中,都引入了线程局部存储变量。后来在C++11中也引入了类似的相关机制并提供了thread_local关键字。它从系统层面上解决了开发者相关的自定义处理,让相关代码变得更容易理解和维护。

二、线程局部存储

线程局部存储即TLS,Thread-Local Storage。它可以让每个线程拥有独立的变量副本,从而隔离多线程间数据的同步问题。可以这样理解,TLS的变量相对每个线程是一个深拷贝,每个线程虽然都拥有相同的变量名字,但其实是完全独立存在的,不会因为某个线程修改自己的此名称变量导致其它线程中的此名称变量的改变。
举一个不太恰当的例子,比如有多个人(多个线程)在打卡机前写考勤,以前只能一个一个人来(需要同步),而后来使用了软件打考勤,每个人打开自己的考勤软件,直接打卡写考勤就可以了(局部存储考勤)。
TLS的应用其实并没想象的那么多,但又是不可或缺的。常见的应用场景有:

  1. 线程需要独立处理各自的数据或变量(如独立随机数和计数器等)
  2. 线程需要独立处理异常和错误
  3. 线程需要处理各自独立的缓存或临时数据

总之,只要开发者需要在线程中对同类型的数据进行独立处理,都可以考虑使用线程的局部存储。

三、底层机制

不同的平台对线程的局部存储有着不同的实现方式,下面就简单分析一下在Windows平台和Linux平台上的实现:

  1. Windows平台
    它分为两种实现情况,一种是静态的TLS,需要编译器层级的支持,实现的方法如下:
__declspec(thread)inttlsDemo;

其主要原理为编译器在编译的.tls段中为每个TLS变量分配地址空间,并在线程启动时,由系统处理相关的TLS块,然后线程中可以通过TEB(Thread Environment Block)对相关变量进行操作管理。
有静态就会有动态,这就需要使用相关的API来操作了,在Windows上提供了下面的API来进行动态的TLS创建:

//get valueint*value=(int*)HeapAlloc(GetProcessHeap(),0,sizeof(int));*value=100;//alloc tls varDWORD tlsDemo=TlsAlloc();//set value to tlsvalueTlsSetValue(tlsDemo,value);//get tslvaluevoid*tlsValue=TlsGetValue(tlsDemo);TlsFree(tlsDemo);

动态TLS使用API创建,它通过接口分配独立的TLS索引并进行相关的TLS值的读写操作,并提供了相关的释放回收函数。在每个进程中,会维护着一组的TLS索引,一般是64个。然后进程中的线程可以独立的通过索引来找到各自的局部存储位置(其实就是把开发者的绑定转移到了系统中绑定)。
动态和静态的TLS各有优势,前者更灵活方便,可以动态的处理上下文的数据;而后者则由编译器和系统自动控制,使用起来更简单。需要哪种就使用哪种,混合使用也不是不行。
2. Linux平台
Linux本身并没有线程这个概念,不过在Posix库提供了类似的实现。其基本的实现方式也有两种,先介绍静态实现:

//ELF格式下的支持__threadinttls_demo=0;

这是一种早期的实现,其实现机制与Windows平台类似,将相关的数据变量分配到ELF的.tdata(初始化)和 .tbss(未初始化)中,然后通过TCB(Thread Control Block)对数据线程局部存储变量进行操作管理。在Linux平台上可以使用readelf等工具对相关的数据段进行访问查看。
其动态创建的方法也是通过相关的API来实现:

pthread_key_tkey;pthread_key_create(&key,free);int*value=malloc(sizeof(int));*value=100;pthread_setspecific(key,value);int*tls_value=(int*)pthread_getspecific(key);free(tls_value);pthread_key_delete(key);
  1. C++ STL
    在C++11后,STL提供了thread_local关键字来支持线程的局部存储。应该了解的是,这种底层实现机制标准库一般都是依赖于系统平台本身的实现,否则无法和系统契合,不能保证相关的安全处理机制。换句话说,C++11中的thread_local,底层就是根据不同的平台调用不同平台的相关线程局部存储的处理机制来实现的。
    在Linux平台一般是通过Posix和Glibc来提供TLS的实现;在Windows平台使用上面提到的相关动、静态 方式实现。更具体的底层实现代码,可参看glibc中的源码中的代码。

由上面的分析可以看出,静态的TLS一般是由编译器和系统共同处理的,由系统和运行时库进行共同管理的内存空间,其生命周期与线程同时存在。而动态的TLS其生命周期由开发者自行控制。另外,线程的局部存储一般不会太大,和栈空间的限制一样,磢在KB级别,这需要开发者引起注意。

四、具体应用

一般来说,标准库有的,都会推荐使用标准库的实现,看一下C++11 thread_local的示例:

#include<iostream>#include<mutex>#include<string>#include<thread>thread_localunsignedintrage=1;std::mutex cout_mutex;voidincrease_rage(conststd::string&thread_name){++rage;// modifying outside a lock is okay; this is a thread-local variablestd::lock_guard<std::mutex>lock(cout_mutex);std::cout<<"Rage counter for "<<thread_name<<": "<<rage<<'\n';}intmain(){std::threada(increase_rage,"a"),b(increase_rage,"b");{std::lock_guard<std::mutex>lock(cout_mutex);std::cout<<"Rage counter for main: "<<rage<<'\n';}a.join();b.join();}

thread_local更类似于上面分析的静态实现,它不需要开发者主动去参与管理,这一点和栈有点类似。

五、总结

其实越是分析C++相关的知识,就会发现其内部体系也是一步步的逐渐建立起来的。这也符合人们的普遍认知,大厦不是一夜建成的。所以标准在不断的迭代,不断的完善旧的体系,创建新的体系,并在合适的时机升级成新的体系。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/22 13:05:27

【课程设计/毕业设计】基于nodejs的半亩菜园线上预售系统的设计与实现【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/2/20 2:21:41

基于单片机的振动检测仪的设计

文章目录摘要一、系统功能二、总体设计三、效果图源码获取摘要 振动监测系统在工程领域具有广泛的应用价值。本文设计了一种基于单片机的振动监测系统&#xff0c;旨在实时监测机械设备的运行状况&#xff0c;并在设备出现故障时发出预警&#xff0c;以确保设备安全运行。 首先…

作者头像 李华
网站建设 2026/2/23 5:01:08

GB28181视频平台EasyGBS视频质量诊断操作指南:从部署到实操

作为一名长期深耕视频监控领域的开发者&#xff0c;你是否也曾遇到过这些棘手问题&#xff1a;监控画面突然卡顿、视频流中断却找不到根源、设备离线告警延迟导致故障扩大……在传统监控系统中&#xff0c;这些问题往往需要耗费大量时间排查&#xff0c;从设备硬件到网络链路&a…

作者头像 李华