线程本地存储(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. FHRP - 网关冗余协议

    通常情况下,在终端设备进入网络前,都会有一个 Router 充当网络,作为第一跳的网络地址.但假设路由器发生故障,此时终端设备就无法再接入互联网. 为了防止这样的问题,一般会再加入一台路由器充当备份. ...

  2. ES核心概念和原理

    ES:1:倒排索引 基于Document 关键词索引实现 . 根据关键词做索引 相关度 a. 数据结构 i. 包含关键词的Document List ii. 关键词在每个doc中出现的次数 词频 TF ...

  3. Java程序员都要懂得知识点:反射

    摘要:Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语 ...

  4. Java例题_38 自定义函数求字符串长度

    1 /*38 [程序 38 求字符串长度] 2 题目:写一个函数,求一个字符串的长度,在 main 函数中输入字符串,并输出其长度. 3 */ 4 5 /*分析 6 * 1.从键盘得到一个字符串 7 ...

  5. Android Studio 之 编写精美的聊天界面

    •准备工作 首先制作一张 .9 格式的聊天气泡,参见我的这篇博客: 需要注意的是,制作完成后,应该将原始文件删除,否则AS会分不清楚而报错. 新建一个 Empty Activity,Java 和 XM ...

  6. CPU 权限划分

    Intel的CPU将特权级别分为4个级别:RING0,RING1,RING2,RING3.Windows只使用其中的两个级别RING0和RING3,RING0只给操作系统用,RING3谁都能用.如果普 ...

  7. 全网最详细的Linux命令系列-ls命令

    Linux开始必须要会的命令当属ls,在日常工作中用到ls命令时的频率是很多的,作为一个初学者,可能我只会或者顶多ls -l两种用法.但是ls其实是一个非常实用的指令,ls命令就是list的缩写,ls ...

  8. 3,turicreate入门 - 优化回归模型,使得预测更准确

    turicreate入门系列文章目录 1,turicreate入门 - jupyter & turicreate安装 2,turicreate入门 - 一个简单的回归模型 3,turicrea ...

  9. 以Aliyun体验机为例,从零搭建LNMPR环境(下)

    使用云服务器搭建 Web 运行环境,尤其是搭建常见的 LNMPR(Linux+Nginx+MySQL+PHP+Redis) 环境,对于开发人员是必备的职场基本技能之一.在这里,借着搭建我的" ...

  10. 22. VUE 插槽-详解

    插槽 一直对插槽不理解,今天学习,并整理一下,希望日后可以灵活运用. (一)插槽内容 先简单来个例子,看一下插槽的租作用. 1.1 不使用插槽 父组件中: <div id="app&q ...