线程本地存储(TLS)

对于多线程应用程序,如果线程过于依赖全局变量和静态局部变量就会产生线程安全问题。也就是一个线程的使用全局变量可能会影响到其他也使用此全局变量的线程,有可能会造成一定的错误,这可以通过线程同步机制解决当然也可以通过线程本地存储解决。线程本地存储意思是每一个线程在使用全局变量的时候都产生一个只属于本线程的副本,这样我们就可以直接使用这个副本,而不会影响到其他线程。

TLS分类

动态TLS

动态TLS涉及四个函数:

TlsAlloc()                               //分配可用的TLS索引并初始化索引
TlsFree(DWORD dwTlsIndex) //释放TLS索引,下次可继续使用
TlsSetValue(DWORD dwTlsIndex,LPVOID p) //设置TLS索引的值
TlsGetValue(DWORD dwTlsIndex) //获取tls索引的值

windows系统为每一个线程都预留了一个TLS数组,这个TLS数组的每一个索引都可以存储数据。我们需要先申请一个可用的索引,这个索引在任何一个线程都可以使用,而且因为其是不同的TLS数组所以同一个索引所代表的并不是同一个内存块,所以其不会相互影响。TLS的内部数据结构如下。

下面通过一个例子来验证:主线程申请一个可用的TLS索引,主线程设置此TLS索引的值为1,接着创建一个子线程并且子线程设置TLS索引为2。

#include <Windows.h>
#include <process.h>
#include <stdio.h>
DWORD dwTlsIndex;
//子线程函数
unsigned int _stdcall _ThreadProc(PVOID pvParam)
{
DWORD dwNum; TlsSetValue(dwTlsIndex, (PVOID)2); //子线程设置TLS索引的值
dwNum = (DWORD)TlsGetValue(dwTlsIndex);
printf("子线程:%d\n", dwNum);
return 0;
} int main(int argc, char* argv[])
{
DWORD dwNum;
uintptr_t hThread; dwTlsIndex = TlsAlloc(); //申请一个索引
if(dwTlsIndex != -1)
{ TlsSetValue(dwTlsIndex, (PVOID)1); //主线程设置TLS索引的值
dwNum = (DWORD)TlsGetValue(dwTlsIndex);
printf("主线程:%d\n", dwNum); hThread = _beginthreadex(NULL, NULL, _ThreadProc, 0, NULL, NULL);
Sleep(100); dwNum = (DWORD)TlsGetValue(dwTlsIndex);
printf("主线程:%d\n", dwNum); TlsFree(dwTlsIndex); //释放一个索引
}
return 0;
}

通过运行结果可以看到在子线程改变TLS索引对应的值为2后,主线程TLS索引对应的值依然为1。两个线程不会相互影响实现了线程本地存储。

我们用OD调试查看TlsSetValue(),发现TLS数组位于线程的fs:[0] + 0xE10地址处。接着通过TlsAlloc()获得的索引就可以访问对应的数组项了。因为每一个线程的fs不同,所以其TLS数组是不同的内存块互补影响。

静态TLS

静态TLS就是利用.tls区段,将全局变量声明为线程本地全局变量并将其放入.tls区段中。只需在声明全局变量时加上关键字 _declspec(thread)就行了。

_declspec(thread) int iTest = 0;

下面通过一个例子来查看静态TLS,主线程使用的线程本地全局变量和定义的线程本地全局变量是同一块内存,而子线程使用的线程本地全局变量就和他们不是一个内存块而是其副本。

#include <Windows.h>
#include <process.h>
#include <stdio.h>
//静态TLS
_declspec(thread) int iTest = 0;
unsigned int _stdcall ThreadProc(PVOID pvParam)
{
iTest = 3;
printf("0x%x:%d\n",&iTest,iTest);
return 0;
} int main(int argc, char* argv[])
{
uintptr_t hThread;
iTest = 5;
printf("0x%x:%d\n",&iTest,iTest);
hThread = _beginthreadex(NULL, NULL, ThreadProc, NULL, NULL, NULL);
Sleep(100);
return 0;
}



下面我们用OD调试程序来看一下TLS的内存布局。

通过在PE文件头中的数据目录表中找到TLS结构,得到TLS表的基地址和大小。



接着来到TLS表处,TLS表是一个IMAGE_TLS_DIRECTORY32的结构(此结构一般在.rdata区块中),查看AddressOfIndex的值为0x52713C

typedef struct _IMAGE_TLS_DIRECTORY32
{
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
PDWORD AddressOfIndex; //用来定位线程局部数据的索引
PIMAGE_TLS_CALLBACK *AddressOfCallBacks; //TLS回调函数指针数组的地址
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32

我们运行程序,可以看到当在主线程中对线程局部数据赋值时,其是将[fs:[0x2C]] + 0x104作为线程局部存储内存块的基地址,而0x52713c作为索引值,也就是TLS结构中的AddressOfIndex作为索引值。

子线程的[fs:[0x2C]] + 0x104则会指向另一个内存块,所以二者不会相互影响。也就是没创建一个新线程,系统就会开辟一块新的内存空间,将.tls区块中的线程局部变量复制到这块空间中,接着会将这块内存空间的地址保存在fs:[0x2C]中。

TLS线程回调函数

每当线程创建或结束前(包括主线程)都会调用TLS线程回调函数,程序可以包含多个TLS线程回调函数,这些函数的地址构成一个数组。数组的首地址保存在IMAGE_TLS_DIRECTORY3结构中的AddressOfCallBacks字段中。

线程本地存储(动态TLS和静态TLS)的更多相关文章

  1. 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理

    原文链接地址:http://www.cppblog.com/Tim/archive/2012/07/04/181018.html 本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们 ...

  2. 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理

    本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们知道在一个进程中,所有线程是共享同一个地址空间的.所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线 ...

  3. Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic

    Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic 1.1. ThreadLocal 设计模式1 1.2. ...

  4. 线程本地存储(Thread Local Storage, TLS)简单分析与使用

    在多线程编程中, 同一个变量, 如果要让多个线程共享访问, 那么这个变量可以使用关键字volatile进行声明; 那么如果一个变量不想使多个线程共享访问, 那么该怎么办呢? 呵呵, 这个办法就是TLS ...

  5. ThreadLocal(线程本地存储)

    1. ThreadLocal,即线程本地变量或线程本地存储. threadlocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的 ...

  6. 线程本地存储 ThreadLocal

    线程本地存储 · 语雀 (yuque.com) 线程本地存储提供了线程内存储变量的能力,这些变量是线程私有的. 线程本地存储一般用在跨类.跨方法的传递一些值. 线程本地存储也是解决特定场景下线程安全问 ...

  7. .NET:线程本地存储、调用上下文、逻辑调用上下文

    .NET:线程本地存储.调用上下文.逻辑调用上下文 目录 背景线程本地存储调用上下文逻辑调用上下文备注 背景返回目录 在多线程环境,如果需要将实例的生命周期控制在某个操作的执行期间,该如何设计?经典的 ...

  8. C# 线程本地存储 调用上下文 逻辑调用上下文

    线程本地存储 using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleAppTest ...

  9. Java线程本地存储ThreadLocal

    前言 ThreadLocal 是一种 无同步 的线程安全实现 体现了 Thread-Specific Storage 模式:即使只有一个入口,内部也会为每个线程分配特有的存储空间,线程间 没有共享资源 ...

随机推荐

  1. Redis不是一直号称单线程效率也很高吗,为什么又采用多线程了?

    Redis是目前广为人知的一个内存数据库,在各个场景中都有着非常丰富的应用,前段时间Redis推出了6.0的版本,在新版本中采用了多线程模型. 因为我们公司使用的内存数据库是自研的,按理说我对Redi ...

  2. 2、MyBatis教程之第一个MyBatis程序

    3.MyBatis第一个程序 1.搭建实验数据库 CREATE DATABASE `mybatis`; USE `mybatis`; DROP TABLE IF EXISTS `user`; CREA ...

  3. (数据科学学习手札114)Python+Dash快速web应用开发——上传下载篇

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...

  4. 开源项目月刊《HelloGitHub》第 60 期

    兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这是一个面向编程新手.热爱编程.对开源社区感兴趣 人群的月刊,月刊的内容包括:各种编 ...

  5. Git 在解决冲突的时候文件覆盖

    更新代码导致被还原或覆盖的场景:1.触发冲突的必要条件是修改同一个文件且修改的位置非常近,否则Git会自动合并其内容避免更新代码导致被还原或覆盖的解决方案1.少修改的地方(生产环境.公网测试环境):推 ...

  6. Linux命令的应用

    目录 Linux命令 Linux文件管理命令 用户管理 权限管理 vi文本编辑器 find查找命令 磁盘管理命令 压缩及解压 Linux 进程 Linux运行tomcat Linux安装mysql 卸 ...

  7. VIM 编辑器操作详解

    1 vim 使用介绍 1.1 vim 安装 # CentOS 安装: yum install -y vim # Ubuntu 安装: sudu apt-get install vim 安装完成后,可使 ...

  8. elementui 表格 如何使操作中隐藏一个按钮

    <el-table-column label="权限"min-width="100"> <template scope="scope ...

  9. 201871030118-雷云云 实验二 个人项目—D{0-1}背包问题项目报告

    项目 内容 课程班级博客链接 班级博客 这个作业要求链接 作业链接 我的课程学习目标 1.了解并掌握psp2.掌握软件项目个人开发流程3.掌握Github发布软件项目的操作方法 这个作业在哪些方面帮助 ...

  10. OO第二单元作业——魔鬼电梯

    简介 本单元作业分为三次 第一次作业:第一次作业要实现单部简单电梯,停靠所有楼层,无载客容量,性能分考量电梯运行时间. 第二次作业: 第二次作业实现多部电梯,电梯数量由初始化设定,每部电梯都停靠所有楼 ...