聊聊Thread Local Storage
聊聊ThreadLocal
- 为什么需要Thread Local Storage
- Thread Local Storage的实现
- PThread库实现
- 操作系统实现
- GCC __thread关键字实现
- C++11 thread_local实现
- JAVA ThreadLocal实现
Thread Local Storage 线程局部存储,简称TLS。
为什么需要Thread Local Storage
变量分为全局变量和局部变量。
- 全局变量:在全局范围内有效,其生命周期跟程序进程的生命周期一致,即在程序启动时初始化,在程序结束时被销毁。
- 局部变量:只在某段代码块内有效,其生命周期是代码块被执行期间,即在进入该段代码块时初始化,离开该段代码块时销毁(对自带垃圾回收的语言,这个销毁会有点滞后)。
全局变量可以用于在多线程间传递数据,非常方便,但需要考虑并发访问冲突问题,一般都需要同步代码块/加锁访问。局部变量只能在代码块内访问,在多线程间互不干扰,无须考虑并发访问冲突问题。
在日常工作中,我们可能会碰到以下场景:希望每个线程拥有自己的变量副本(Thread Local Storage),这样该变量(也称为ThreadLocal变量)在线程间互不干扰,从而避免并发访问冲突问题。
比如随机数生成场景中,生成的伪随机数生成,即当随机数种子固定后,那么生成的随机数序列都是固定的。为了保证随机数的随机性,就可以将随机数种子声明为ThreadLocal,这样在不同的线程中,这些随机数种子不同,从而不同线程生成的随机数序列也不同。
比如linux系统中的errno变量,该变量是全局变量,很早之前都是单线程模型,errno的用法没问题,但后来支持多线程了,errno变量值就受到多线程干扰了,为了保证多线程的errno能正确返回,只能通过Thread Local Storage的方式,无法通过加锁的方式保证。
Thread Local Storage的实现
Thread Local Storage的本质就是每个线程都有该变量副本。
PThread库实现
在C语言中,可以使用Pthread库来实现线程局部存储。
Pthread库提供了一种称为线程特定数据(Thread-Specific Data, TSD)的机制,允许每个线程关联一组键值对。每个线程可以通过键来访问和修改其关联的值,而不会影响其他线程中的相同键的值。
在内部,Pthread库通常会为每个线程维护一个线程局部存储的数据结构(如哈希表),用于存储键值对。每个线程在访问或修改其局部存储的数据时,都会通过这个数据结构进行操作。

为了使用TSD的特性,Pthread库提供了以下方法
//创建键,即获取一个keys数组的索引
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
//设置键关联的数据
int pthread_setspecific(pthread_key_t key, const void *value);
//获取键关联的数据
void *pthread_getspecific(pthread_key_t key);
//释放键,即重置键关联keys数组中对应的值,以便其他变量使用
int pthread_key_delete(pthread_key_t key);
- 键(Key)的创建和管理
使用pthread_key_create函数可以创建一个键,该键可以被多个线程共享。创建键时,可以指定一个析构函数,当线程结束时,该函数会被调用来释放与键关联的数据。 - 数据的设置和获取
使用pthread_setspecific函数可以将数据与特定的键和线程关联起来。使用pthread_getspecific函数可以获取与特定键和线程关联的数据。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>// 定义一个键
pthread_key_t key;// 线程函数
void* thread_func(void* arg) {int* data = (int*)malloc(sizeof(int));*data = *(int*)arg;pthread_setspecific(key, data);// 获取并打印线程局部存储的数据int* retrieved_data = (int*)pthread_getspecific(key);printf("Thread %ld: data = %d\n", pthread_self(), *retrieved_data);return NULL;
}int main() {pthread_t thread1, thread2;int data1 = 10, data2 = 20;// 创建键pthread_key_create(&key, free);// 创建线程pthread_create(&thread1, NULL, thread_func, &data1);pthread_create(&thread2, NULL, thread_func, &data2);// 等待线程结束pthread_join(thread1, NULL);pthread_join(thread2, NULL);// 销毁键pthread_key_delete(key);return 0;
}
操作系统实现
操作系统在实现Thread Local Storage机制上主要考虑以下方面:
- TLS数据结构的分配
操作系统为每个线程分配一个独立的TLS区域,用于存储该线程的所有TLS变量。在编译期间可以确定TLS变量个数,所以这个区域通常是一个固定大小的内存块。 - TLS变量的存储
每个线程可能会访问多个TLS变量,操作系统会为每个TLS变量分配一个唯一的偏移量,这个偏移量表示该变量在TLS区域中的位置。线程可以通过这个偏移量访问自己的TLS变量。 - TLS变量的访问
当线程需要访问一个TLS变量时,操作系统会提供一组特殊的指令或函数,用于从当前线程的TLS区域中获取该变量的值。这些指令或函数通常会使用线程ID和TLS变量的偏移量来计算变量的实际地址。 - TLS变量的初始化
操作系统会在每个线程开始执行时自动初始化TLS变量。对于全局范围的TLS变量,操作系统会在进程启动时为其分配内存并进行初始化。对于函数范围内的TLS变量,操作系统会在函数调用时为其分配内存并进行初始化。 - TLS变量的销毁
当线程结束时,操作系统会自动回收其TLS区域,并释放相应资源。
GCC __thread关键字实现
GCC通过使用操作系统提供的线程局部存储(Thread Local Storage,TLS)机制来实现**__thread关键字**。__thread关键字用于声明线程局部变量。这些变量在每个线程中都有独立的实例,互不干扰。当线程结束时,这些变量的生命周期也随之结束。
以下是GCC实现__thread关键字的一些关键步骤:
- 生成TLS变量
当你在代码中使用__thread关键字声明一个变量时,GCC会为该变量生成一个TLS符号。这个符号在程序的整个生命周期内都存在,但在不同的线程中具有不同的值。
例如:
__thread int counter = 0;
编译后,GCC会生成一个类似于_ZL7counter的TLS符号。 - 分配TLS空间
在程序启动时,操作系统会为每个线程分配一块TLS空间。这块空间的大小取决于程序中声明的TLS变量的数量。GCC会在程序初始化时计算所需的TLS空间大小,并将其传递给操作系统。Linux默认最大只支持1024个TLS变量。 - 访问TLS变量
当线程访问一个__thread变量时,GCC会生成一段特殊的代码,用于从当前线程的TLS空间中获取该变量的值。这段代码通常是一个内存访问指令,其地址由线程ID和TLS偏移量计算得出。
例如,访问上面的counter变量时,GCC可能会生成类似以下的代码:
movl $_ZL7counter@TLSGD(%rip), %eax
这段代码将当前线程的TLS空间中counter变量的值加载到寄存器%eax中。 - 初始化TLS变量
GCC会在每个线程开始执行时自动初始化__thread变量。对于全局范围的__thread变量,GCC会在程序启动时为其分配内存并进行初始化。对于函数范围内的静态变量,GCC会在首次调用时为其分配内存并进行初始化。 - 销毁TLS变量
当线程结束时,操作系统会自动回收其TLS空间,并释放相应资源。
__thread的使用限制
- 只能修饰POD类型(类似整型指针的标量,不带自定义的构造、拷贝、赋值、析构的类型,二进制内容可以任意复制memset,memcpy,且内容可以复原)。
- 不能修饰class类型,因为无法自动调用构造和析构函数。
- 可用于修饰全局变量,函数内的静态变量,不能修饰函数的局部变量或class的普通成员变量。
- __thread变量值只能初始化为编译器常量
- __thread限定符(specifier)可以单独使用,也可带有extern或static限定符,但不能带有其它存储类型的限定符。
- __thread可用于全局的静态文件作用域,静态函数作用域或一个类中的静态数据成员。不能用于块作用域,自动或非静态数据成员。
C++11 thread_local实现
c++11提供的thread_local实现跟GCC __thread实现类似,都是借助操作系统的TLS机制实现的。但是c++11提供的thread_local可跨平台使用,也可修饰非POD类型的变量。
#include <iostream>
#include <thread>// 声明一个线程局部变量
thread_local int thread_local_var = 0;void thread_function(int thread_id) {// 更新线程局部变量的值thread_local_var = thread_id;std::cout << "Thread " << thread_id << ": thread_local_var = " << thread_local_var << std::endl;
}int main() {// 创建两个线程std::thread t1(thread_function, 1);std::thread t2(thread_function, 2);// 等待线程结束t1.join();t2.join();return 0;
}
JAVA ThreadLocal实现
Java采用的实现方案跟上面类似,也是每个线程一个数组,专门用来存储变量副本。

由图可知,每个线程使用ThreadLocalMap存储ThreadLocal对应的具体值,在读写ThreadLocal变量对应的值时,最终都是到table中读写。由于不同线程的table不一样,虽然ThreadLocal变量一致,但是对应的值不一样,这样就实现了不同线程有不同的数据副本。
相关文章:
聊聊Thread Local Storage
聊聊ThreadLocal 为什么需要Thread Local StorageThread Local Storage的实现PThread库实现操作系统实现GCC __thread关键字实现C11 thread_local实现JAVA ThreadLocal实现 Thread Local Storage 线程局部存储,简称TLS。 为什么需要Thread Local Storage 变量分为全…...
WEB攻防-JS项目Node.js框架安全识别审计验证绕过
知识点: 1、原生JS&开发框架-安全条件 2、常见安全问题-前端验证&未授权 详细点: 1、什么是JS渗透测试? 在JavaScript中也存在变量和函数,当存在可控变量及函数调用即可参数漏洞 2、流行的Js框架有哪些? …...
STM32——SPI
1.SPI简介 SPI,是英语Serial Peripheral Interface的缩写,顾名思义就是串行外围设备接口。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚…...
【云安全】云上资产发现与信息收集
一、云基础设施组件 1、定义 在云计算基础架构中,基础设施组件通常包括:计算、存储、网络和安全等方面的资源。例如,计算资源可以是虚拟机、容器或无服务器计算引擎;存储资源可以是对象存储或块存储;网络资源可以是虚拟…...
flask搭建微服务器并训练CNN水果识别模型应用于网页
一. 搭建flask环境 概念 flask:一个轻量级 Web 应用框架,被设计为简单、灵活,能够快速启动一个 Web 项目。CNN:深度学习模型,用于处理具有网格状拓扑结构的数据,如图像(2D网格)和视频(3D网格&a…...
数据篇| 关于Selenium反爬杂谈
友情提示:本章节只做相关技术讨论, 爬虫触犯法律责任与作者无关。 LLM虽然如火如荼进行着, 但是没有数据支撑, 都是纸上谈兵, 人工智能的三辆马车:算法-数据-算力,缺一不可。之前写过关于LLM微调文章《微调入门篇:大模型微调的理论学习》、《微调实操一: 增量预训练(Pretrai…...
MySQL高阶1890-2020年最后一次登录
目录 题目 准备数据 分析数据 题目 编写解决方案以获取在 2020 年登录过的所有用户的本年度 最后一次 登录时间。结果集 不 包含 2020 年没有登录过的用户。 返回的结果集可以按 任意顺序 排列。 准备数据 Create table If Not Exists Logins (user_id int, time_stamp …...
update-alternatives官方手册
下述手册超链接都是英文,内容差不多,看一个就行 Debian系统的Ubuntu系统的《The Linux Programming Interface》图书上的...
cesium.js 入门到精通(5-2)
在cesium 的配置中 有一些参数 可以配置地图的显示 显示出 水的动态显示 山的效果 相当于一些动画显示的效果 var viewer new Cesium.Viewer("cesiumContainer", {infoBox: false,terrainProvider: await Cesium.createWorldTerrainAsync({requestWaterMask: tru…...
LINUX的PHY抽象层——PAL
英文原文参考: https://www.kernel.org/doc/html/latest/networking/phy.html 中文翻译参考:有关PHY抽象层的总结 https://blog.csdn.net/eydwyz/article/details/124753313 目录 1 前言2 PHY接口模式3 尽量使用PHY端的延时而不是MAC或PCB4 其他方式实现…...
优先级队列(堆)
目录 优先级队列 堆的概念 堆的创建 堆的向下调整 堆的插入 完整代码 优先级队列 队列是一种先进先出的数据结构,有些时候操作的数据可能带有优先级,出队列时就需要优先级高的数据先出队列。 在这种情况下,数据结构应该提供两个最基本…...
帧率和丢帧分析理论
一、丢帧问题概述 应用丢帧通常指的是在应用程序的界面绘制过程中,由于某些原因导致界面绘制的帧率下降,从而造成界面卡顿、动画不流畅等问题。以60Hz刷新率为例子,想要达到每秒60帧(即60fps)的流畅体验,每…...
solidwork找不到曲面
如果找不到曲面 则右键找到选项卡,选择曲面...
mac安装JetBtains全家桶新版本时报错:Cannot start the IDE
mac安装JetBtains全家桶新版本时报错:Cannot start the IDE 前言报错信息解决方法 前言 作者使用的是Mac电脑,最近想要更新JetBrains相关工具的软件版本,但是在安装时突然报错,导致安装失败,现在将报错信息以及解决方…...
MVCC机制解析:提升数据库并发性能的关键
MVCC机制解析:提升数据库并发性能的关键 MVCC(Multi-Version Concurrency Control) 多版本并发控制 。 MVCC只在事务隔离级别为读已提交(Read Committed)和可重复读(Repeated Read)下生效。 MVCC是做什么用的 MVCC是为了处理 可重复读 和…...
如何使用Postman搞定带有token认证的接口实战!
现在许多项目都使用jwt来实现用户登录和数据权限,校验过用户的用户名和密码后,会向用户响应一段经过加密的token,在这段token中可能储存了数据权限等,在后期的访问中,需要携带这段token,后台解析这段token才…...
Linux Vim编辑器常用命令
目录 一、命令模式快捷键 二、编辑/输入模式快捷键 三、编辑模式切换到命令模式 四、搜索命令 注:本章内容全部基于Centos7进行操作,查阅本章节内容前请确保您当前所在的Linux系统版本,且具有足够的权限执行操作。 一、命令模式快捷键 二…...
【Android】浅析MVC与MVP
【Android】浅析MVC与MVP 什么是架构? 架构(Architecture)在软件开发中指的是软件系统的整体设计和结构,它描述了系统的高层组织方式,包括系统中各个组件之间的关系、依赖、交互方式,以及这些组件如何协同…...
spark 面试题
spark 面试题 1、spark 任务如何解决第三方依赖 比如机器学习的包,需要在本地安装?--py-files 添加 py、zip、egg 文件不需要在各个节点安装 2、spark 数据倾斜怎么解决 spark 中数据倾斜指的是 shuffle 过程中出现的数据倾斜,主要是由于…...
青柠视频云——如何开启HTTPS服务?
前言 由于青柠视频云的语音对讲会使用到HTTPS服务,这里我们说一下如何申请证书以及如何在实战中部署并且配置使用。 一、证书申请 1、进入控制台 我们拿阿里云的免费个人证书为例,首先登录阿里云,在控制台找到数字证书管理服务,进…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
Python爬虫实战:研究Restkit库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...
大数据驱动企业决策智能化的路径与实践
📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:数据驱动的企业竞争力重构 在这个瞬息万变的商业时代,“快者胜”的竞争逻辑愈发明显。企业如何在复杂环…...
