线程本地存储(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. 08、元组tuple

    元组(tuple) 是一个有序且不可变的容器,在里面可以存放多个不同类型的元素 元组是在最后多一个逗号,用于表示它是一个元组 tuple = (11,22,'阿斯顿','媚媚',) #后面多加一个逗号 ...

  2. 从键盘读入学生成绩,找出最高分, 并输出学生成绩等级(Java)

    从键盘读入学生成绩,找出最高分, 并输出学生成绩等级 一.题目 从键盘读入学生成绩,找出最高分,并输出学生成绩等级. 成绩>=最高分-10 等级为'A' 成绩>=最高分-20 等级为'B' ...

  3. Python基础之数据类型详解(2)

    今天继续昨天的python基本数据类型详解,按照上一篇博文的格式,接下来讲解列表.元组.字典以及集合. 列表 1.用途按位置存放多个值2.定义在[]内用逗号分割开多个任意类型的元素 # 定义列表 # ...

  4. kubernetes1.17.2结合ceph13.2.8部署gitlab12.1.6

    [root@bs-k8s-ceph ~]# ceph -s cluster: id: 11880418-1a9a-4b55-a353-4b141e2199d8 health: HEALTH_OK se ...

  5. 快速排序(QuickSort)Java版

    快速排序 快速排序是对冒泡排序的一种改进. 它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排 ...

  6. [Skill]从零掌握正则表达式

    前言 无论你是出于什么原因需要掌握正则表达式(诸如爬虫.文本检索.后端服务开发或Linux脚本),如果之前从没接触过正则表达式(比如我)很容易在如山般的公式中迷失,以至于你在项目写的正则表达式很可能会 ...

  7. JAVAEE_Servlet_06_ServletContext接口

    ServletContext接口 * javax.servlet.ServletContext * Tomcat服务器中ServletContecxt的完整类名: ServletContext:org ...

  8. Jenkins 系统管理与配置

    1. Jenkins 安装插件的两种方式 2. 添加凭据(Credentials) 3. 系统管理--全局工具配置 4. 系统管理--系统设置 5. 常用插件说明 Extended E-mail No ...

  9. 阿里云 RTC QoS 弱网对抗之 LTR 及其硬件解码支持

    LTR 弱网对抗由于需要解码器的反馈,因此用硬件解码器实现时需要做一些特殊处理.另外,一些硬件解码器对 LTR 的实现不是特别完善,会导致出现解码错误.本文为 QoS 弱网优化系列的第三篇,将为您详解 ...

  10. 《疯狂Kotlin讲义》读书笔记6——函数和Lambda表达式

    函数和Lambda表达式 Kotlin融合了面向过程语言和面向对象语言的特征,相比于Java,它增加了对函数式编程的支持,支持定义函数.调用函数.相比于C语言,Kotlin支持局部函数(Lambda表 ...