聊聊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、进入控制台 我们拿阿里云的免费个人证书为例,首先登录阿里云,在控制台找到数字证书管理服务,进…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...

Redis上篇--知识点总结
Redis上篇–解析 本文大部分知识整理自网上,在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库,Redis 的键值对中的 key 就是字符串对象,而 val…...
day51 python CBAM注意力
目录 一、CBAM 模块简介 二、CBAM 模块的实现 (一)通道注意力模块 (二)空间注意力模块 (三)CBAM 模块的组合 三、CBAM 模块的特性 四、CBAM 模块在 CNN 中的应用 一、CBAM 模块简介 在之前的探索中…...
leetcode 386. 字典序排数 中等
给你一个整数 n ,按字典序返回范围 [1, n] 内所有整数。 你必须设计一个时间复杂度为 O(n) 且使用 O(1) 额外空间的算法。 示例 1: 输入:n 13 输出:[1,10,11,12,13,2,3,4,5,6,7,8,9]示例 2: 输入:n 2…...