聊聊Thread Local Storage

news2024/12/28 4:55:56

聊聊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采用的实现方案跟上面类似,也是每个线程一个数组,专门用来存储变量副本。
ThreadLocal实现
由图可知,每个线程使用ThreadLocalMap存储ThreadLocal对应的具体值,在读写ThreadLocal变量对应的值时,最终都是到table中读写。由于不同线程的table不一样,虽然ThreadLocal变量一致,但是对应的值不一样,这样就实现了不同线程有不同的数据副本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2151377.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

元学习的简单示例

代码功能 模型结构&#xff1a;SimpleModel是一个简单的两层全连接神经网络。 元学习过程&#xff1a;在maml_train函数中&#xff0c;每个任务由支持集和查询集组成。模型先在支持集上进行训练&#xff0c;然后在查询集上进行评估&#xff0c;更新元模型参数。 任务生成&…

STM32G431RBT6(蓝桥杯)串口(发送)

一、基础配置 (1) PA9和PA10就是串口对应在单片机上的端口 注意&#xff1a;一定要先选择PA9的TX和PA10的RX&#xff0c;再去打开异步的模式 (2) 二、查看单片机的端口连接至电脑的哪里 &#xff08;1&#xff09;此电脑->右击属性 &#xff08;2&#xff09;找到端…

AI视觉算法盒是什么?如何智能化升级网络摄像机,守护全方位安全

在智能化浪潮席卷全球的今天&#xff0c;以其创新技术引领行业变革&#xff0c;推出的集高效、智能、灵活于一体的AI视觉算法盒。这款革命性的产品&#xff0c;旨在通过智能化升级传统网络摄像机&#xff0c;为各行各业提供前所未有的安全监控与智能分析能力&#xff0c;让安全…

SpringCloud构建工程

一、新建数据库和表&#xff0c;并填写测试数据 二、创建父级工程 1、创建maven工程 2、工程名字OfficeAutomation 3、pom.xml文件中添加依赖 <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.encodin…

领域驱动DDD三种架构-分层架构、洋葱架构、六边形架构

博主介绍&#xff1a; 大家好&#xff0c;我是Yuperman&#xff0c;互联网宇宙厂经验&#xff0c;17年医疗健康行业的码拉松奔跑者&#xff0c;曾担任技术专家、架构师、研发总监负责和主导多个应用架构。 技术范围&#xff1a; 目前专注java体系&#xff0c;以及golang、.Net、…

第二十节:学习Redis缓存数据库实现增删改查(自学Spring boot 3.x的第五天)

这节记录下如何使用redis缓存数据库。 第一步&#xff1a; 先在服务器端安装redis&#xff0c; 下载地址&#xff1a;Releases tporadowski/redis GitHub。 第二步&#xff1a; 安装redis客户端可视化管理软件redisDesktopmanager Redis Desktop Manager - Download 第…

GAMES101(13节Ray Tracing)

Ray Tracing 基本原理&#xff1a; 我们知道为什么会看到物体的颜色&#xff0c;因为光线照射物体&#xff0c;未被吸收的光线反射到人眼&#xff0c;因此&#xff0c;我们看到的颜色&#xff0c;就是光的一部分&#xff0c;光线追踪就是模拟这个过程 光线假设&#xff1a; …

DHCP协议原理(网络协议)

DHCP简介 定义 DHCP&#xff08;动态主机配置协议&#xff09;是一种网络管理协议&#xff0c;能够自动为局域网中的每台计算机分配IP地址及其他网络配置参数&#xff0c;包括子网掩码、默认网关和DNS服务器等。这一机制极大简化了网络管理&#xff0c;尤其在大型局域网中&am…

聊聊AUTOSAR:基于Vector MICROSAR的TC8测试开发方案

技术背景 车载以太网技术作为汽车智能化和网联化的重要组成部分&#xff0c;正逐步成为现代汽车网络架构的核心&#xff0c;已广泛应用于汽车诊断&#xff08;如OBD&#xff09;、ECU软件更新、智能座舱系统、高清摄像头环视泊车系统等多个领域。 在这个过程中&#xff0c;ET…

CSS 的元素显示模式简单学习

目录 1. 元素显示模式 1.1 概述 1.2 块元素 1.3 行元素 1.4 行内块元素 1.5 元素显示模式总结 2. 元素显示模式转换 3. 单行文字垂直居中 4. 案例演示 1. 元素显示模式 1.1 概述 1.2 块元素 1.3 行元素 1.4 行内块元素 1.5 元素显示模式总结 2. 元素显示模式转换 3. 单…

通过markdown表格批量生成格式化的word教学单元设计表格

素材&#xff1a; 模板&#xff1a; 代码&#xff1a; import pandas as pd from python_docx_replace import docx_replace,docx_get_keys from docx import Document from docxcompose.composer import Composerdef parse_markdown_tables(file_path):with open(file_path,…

DOCKER 数据库管理软件自己开发--———未来之窗行业应用跨平台架构

- 数据异地容灾服务--未来之窗智慧数据服务 DATA REMOTE DISASTER RECOVERY SERVICE -CyberWin Future Docker-数据查看 CyberWin DATA Viewer 1.docker 样式 mysqli://root:密码172.17.0.2:端口/数据库 阿雪技术观 拥抱开源与共享&#xff0c;见证科技进步奇迹&#xff0c;…

AMD小胜!锐龙7 9700X VS. i7- 14700K网游对比

一、前言&#xff1a;两款高端处理器的网游对比测试 半个月前&#xff0c;我们做了锐龙5 9600X与i5-14600K的网游帧率测试&#xff0c;结果有点意外&#xff0c;几款游戏平均下来&#xff0c;锐龙5 9600X比i5-14600K竟然强了19%之多。 今天我们将会对锐龙7 9700X和i7-14700K进行…

【高阶数据结构】二叉搜索树的插入、删除和查找(精美图解+完整代码)

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《高阶数据结构》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多《高阶数据结构》点击专栏链接查看&a…

【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)

文章目录 前言一、ArkTS基本介绍1、 ArkTS组成2、组件参数和属性2.1、区分参数和属性的含义2.2、父子组件嵌套 二、装饰器语法1.State2.Prop3.Link4.Watch5.Provide和Consume6.Observed和ObjectLink代码示例&#xff1a;示例1&#xff1a;&#xff08;不使用Observed和ObjectLi…

Windows11家庭版修改用户密码策略为永不过期。

今天有个朋友找到我说&#xff0c;他的电脑密码老是过期然后需要修改&#xff0c;让我帮忙改一下密码策略&#xff0c;改为永不过期。 下面就来操作一下吧。 这里有个小小的坑&#xff0c;就是win11的家庭版是没有 gpedit.msc的&#xff0c;也就不能直接cmd打开本地策略便器&…

【WebGis开发 - Cesium】获取视野中心点,并设置顶视图视角

引言 项目开发过程中遇到一个需求&#xff0c;通过一个按钮切换视角为顶视图。 分析了一下这个模糊的需求&#xff0c;首先没有给出切换顶视图后俯视的区域范围&#xff0c;其次没有给出俯视点的高度。 这里可以粗略的认为当前的侧俯视的角度下观看的范围即为俯视的区域范围&am…

视频美颜SDK核心功能解析:打造高效直播美颜工具方案详解

随着直播行业的迅猛发展&#xff0c;用户对于直播画质和个人形象的要求越来越高。视频美颜SDK作为一项关键技术&#xff0c;已经成为各大直播平台和短视频应用的重要组成部分。通过实时美颜技术&#xff0c;用户能够在直播过程中呈现出更加理想的形象&#xff0c;从而提升直播体…

实验一:Windows下的IIS服务器配置和管理

第一次实验隐藏关很多&#xff0c;稍不留神服务器就寄了。 实验一完成后会有联网问题&#xff0c;问题解决详见番外篇。 实验内容 任务一&#xff1a; 1、建立一个基于主机名www.study.com的站点&#xff0c;站点的主目录为C:\inetpub\wwwroot&#xff0c;给站点建立一个虚拟…

Codeforces Round 973 (Div. 2) F1. Game in Tree (Easy Version)(思维题 博弈)

题目 思路来源 乱搞ac 题解 两个人的策略是一样的&#xff0c;把1到u的路径标记&#xff0c; 如果能走旁边的链&#xff08;也就是当前点&#xff0c;刨去标记链以外的子树中最长的链&#xff09;&#xff0c; 使得对面走剩余的连通块无法比你大&#xff0c;就走旁边的链&…