线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理
本文为线程本地存储TLS系列之分类和原理。
一、TLS简述和分类
我们知道在一个进程中,所有线程是共享同一个地址空间的。所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线程对其进行了修改,也就会影响到其他所有的线程。不过我们可能并不希望这样,所以更多的推荐用基于堆栈的自动变量或函数参数来访问数据,因为基于堆栈的变量总是和特定的线程相联系的。
不过如果某些时候(比如可能是特定设计的dll),我们就是需要依赖全局变量或者静态变量,那有没有办法保证在多线程程序中能访问而不互相影响呢?答案是有的。操作系统帮我们提供了这个功能——TLS线程本地存储。TLS的作用是能将数据和执行的特定的线程联系起来。
实现TLS有两种方法:静态TLS和动态TLS。以下我们将分别说明这两类TLS。
二、静态TLS
1、使用静态TLS
之所以先讲静态TLS,是因为他在代码中使用时非常简单,我们只需写类似如下这一句:
__declspec(thread) DWORD myTLSData=0;
我们就为本程序中的每一个线程创建了一个独立的DWORD数据。
__declspec(thread)的前缀是Microsoft添加给Visual C++编译器的一个修改符。它告诉编译器,对应的变量应该放入可执行文件或DLL文件中它的自己的节中。__declspec(thread)后面的变量必须声明为函数中(或函数外)的一个全局变量或静态变量。不能声明一个类型为__declspec(thread)的局部变量,你想,因为局部变量总是与特定的线程相联系的,如果再加上这个声明是代表什么意思?
2、静态TLS原理
静态TLS的使用是如此简单,那么当我们写了如上代码以后,操作系统和编译器是怎么处理的呢?
首先,在编译器对程序进行编译时,它会将所有声明的TLS变量放入它们自己的节,这个节的名字是.tls。而后链接程序将来自所有对象模块的所有.tls节组合起来,形成结果的可执行文件或DLL文件中的一个大的完整的.tls节。
然后,为了使含有静态TLS的程序能够运行,操作系统必须参与其操作。当TLS应用程序加载到内存中时,系统要寻找可执行文件中的.tls节,并且动态地分配一个足够大的内存块,以便存放所有的静态TLS变量。应用程序中的代码每次引用其中的一个变量时,就要转换为已分配内存块中包含的一个内存位置。因此,编译器必须生成一些辅助代码来引用该静态TLS变量,这将使你的应用程序变得比较大而且运行的速度比较慢。在x86
CPU上,将为每次引用的静态TLS变量生成3个辅助机器指令。如果在进程中创建了另一个线程,那么系统就要将它捕获并且自动分配另一个内存块,以便存放新线程的静态TLS变量。新线程只拥有对它自己的静态TLS变量的访问权,不能访问属于其他线程的TLS变量。
以上是包含静态TLS变量的可执行文件如何运行的情况。我们再来看看DLL的情况:
a、隐式链接包含静态TLS变量的DLL
如果应用程序使用了静态TLS变量,并且隐式链接包含静态TLS变量的DLL时,当系统加载该应用程序时,它首先要确定应用程序的.tls节的大小,并将这个值与应用程序链接的DLL中的所有.tls节的大小相加。当在你的进程中创建线程时,系统自动分配足够大的内存块来存放所有应用程序声明的和所有隐含链接的DLL包含的TLS变量。
b、显式链接包含静态TLS变量的DLL
考虑一下,当我们的应用程序通过调用LoadLibrary,以便显式链接到包含静态TLS变量的DLL时,会发生什么情况呢?系统必须查看该进程中已经存在的所有线程,并扩大它们的TLS内存块,以便适应新DLL对内存的需求。另外,如果调用FreeLibrary来释放包含静态TLS变量的DLL,那么与进程中的每个线程相关的的TLS内存块又都应该被压缩。
对于操作系统来说,这样的管理任务太重了。所以,虽然系统允许包含静态TLS变量的库在运行期进行显式加载,但是其包含TLS数据却没有进行相应的初始化。如果试图访问这些数据,就可能导致访问违规!
所以,请记住:如果某个DLL包含静态TLS数据,请不要对这个DLL采用显式链接的方式,否则可能会出错!
三、动态TLS
1、使用动态TLS
动态TLS在程序实现中比静态TLS要稍微麻烦一些,需要通过一组函数来实现:
DWORD TlsAlloc();//返回TLS数组可用位置的索引
BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue); //将调用线程的TLS数组索引dwTlsIndex处设为值lpTlsValue
LPVOID TlsGetValue(DWORD dwTlsIndex); //返回调用线程的TLS数组dwTlsIndex索引处的值
BOOL TlsFree(DWORD dwTlsIndex); //释放所有线程的TLS数组位置索引dwTlsIndex,将该位置标记为未使用。
有了以上四个函数,我们可以发现使用动态TLS其实还是很容易很方便的。
2、动态TLS原理
让我们看看windows用来管理TLS的内部数据结构:
线程本地存储器的位标志显示了该进程中所有运行的线程正在使用的一组标志。每个标志均可设置为FREE或者INUSE,表示TLS插槽(slot)是否正在使用。Microsoft保证至少TLS_MINIMUM_AVAILABLE位标志是可供使用的。另外,TLS_MINIMUM_AVAILABLE在WinNT.h中被定义为64。Windows2000将这个标志数组扩展为允许有1000个以上的TLS插槽。
而每一个线程拥有一个自己独立的TLS slot数组,用于存储TLS数据。
为了使用动态TLS,我们首先调用TlsAlloc()来命令系统对进程的位标志进行扫描,找到一个可用的位置,并返回该索引;如果找不到,就返回TLS_OUT_OF_INDEXES。事实上,除此之外,TlsAlloc函数还会自动清空所有线程的TLS数组的对应索引的值。这避免以前遗留的值可能引起的问题。
然后,我们就可以调用TlsSetValue函数将对应的索引位保存一个特定的值,可以调用TlsGetValue()来返回该索引位的值。注意,这两个函数并不执行任何测试和错误检查,我们必须要保证索引是通过TlsAlloc正确分配的。
当所有线程都不需要保留TLS数组某个索引位的时候,应该调用TlsFree。该函数告知系统将进程的位标志数组的index位置为FREE状态。如果运行成功,函数返回TRUE。注意,如果试图释放一个没有分配的索引位,将产生一个错误。
动态TLS的使用相对静态TLS稍微麻烦一点,但是无论是将其用在可执行文件中还是DLL中,都还是很简单的。而且当用在DLL中时,没有由于DLL链接方式而可能产生的问题,所以,如果要在DLL中用TLS,又不能保证客户始终采用隐式链接方式,那么请采用动态TLS的实现。
线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理的更多相关文章
- 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理
原文链接地址:http://www.cppblog.com/Tim/archive/2012/07/04/181018.html 本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们 ...
- 线程局部存储TLS(thread local storage)
同一全局变量或者静态变量每个线程访问的是同一变量,多个线程同时访存同一全局变量或者静态变量时会导致冲突,尤其是多个线程同时需要修改这一变量时,通过TLS机制,为每一个使用该全局变量的线程都提供一个变量 ...
- TLS Thread Local Storage
https://blog.csdn.net/yusiguyuan/article/details/22938671 https://blog.csdn.net/simsunny22/article/d ...
- 线程本地存储(Thread Local Storage, TLS)简单分析与使用
在多线程编程中, 同一个变量, 如果要让多个线程共享访问, 那么这个变量可以使用关键字volatile进行声明; 那么如果一个变量不想使多个线程共享访问, 那么该怎么办呢? 呵呵, 这个办法就是TLS ...
- Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic
Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic 1.1. ThreadLocal 设计模式1 1.2. ...
- 线程本地存储(动态TLS和静态TLS)
线程本地存储(TLS) 对于多线程应用程序,如果线程过于依赖全局变量和静态局部变量就会产生线程安全问题.也就是一个线程的使用全局变量可能会影响到其他也使用此全局变量的线程,有可能会造成一定的错误,这可 ...
- .NET:线程本地存储、调用上下文、逻辑调用上下文
.NET:线程本地存储.调用上下文.逻辑调用上下文 目录 背景线程本地存储调用上下文逻辑调用上下文备注 背景返回目录 在多线程环境,如果需要将实例的生命周期控制在某个操作的执行期间,该如何设计?经典的 ...
- C# 线程本地存储 调用上下文 逻辑调用上下文
线程本地存储 using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleAppTest ...
- ThreadLocal(线程本地存储)
1. ThreadLocal,即线程本地变量或线程本地存储. threadlocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的 ...
随机推荐
- 在 Ubuntu 系统安装 Redi laravel 5.2 引入第三方类
composer 安装类依赖包 很受用 也很方便 但是要是一个有一定规模的公司技术团队 因为要照顾大局 还是引入类好些 下面是引入类的方法 1.首先在app目录下创建一个新的文件夹,命名Tools(可 ...
- ZOJ 3954 Seven-Segment Display
二分图匹配. 先检查每个数字$1$的个数是否满足条件,不满足直接就是无解.剩下的情况可以建立二分图,如果现在的某一列可以对应于原图的某一列,那么建边.如果二分图的最大匹配是$7$,则有解,否则误解. ...
- UVA10298 Power Strings [KMP]
题目传送门 Power Strings 格式难调,题面就不放了. 一句话题意,求给定的若干字符串的最短循环节循环次数. 输入样例#1: abcd aaaa ababab . 输出样例#1: 1 4 3 ...
- 图像 & 视频
Python图像处理库:Pillow 初级教程 用Python做图像处理 python 视频处理,提取视频相关帧,读取Excel
- pycharm的安装教程及大坑
在根据网上的教程创建新工程后,发现不能调用第三方库,网上大多给的是print('hello world'),己适python解释器用的pycharm默认的也不能发现错误.后来浏览了一篇文章才恍然大悟, ...
- 不愿看到Java开发者再做的10件事
William F. Buckley.Jr 曾经说过,“保守主义者是那些逆着历史潮流不断喊停的人,其他人都不愿意这么做或者对他们这么做显得没有耐性”.虽然我对此了解不多,但是每次看到有Java开发人员 ...
- EAP-MD5认证暴力破解工具eapmd5pass
EAP-MD5认证暴力破解工具eapmd5pass EAP-MD5是一种基于802.1x协议的认证机制.由于该机制存在漏洞,所以并不能保证数据安全.Kali Linux预置一个专用工具eapmd5 ...
- Ubuntu下查看软件版本及安装位置
查看软件版本: XXX --version 或 aptitude show xxx 也可用apt-show-versions (要先安装sudo apt-get install apt-show-ve ...
- 我的OI生涯 第七章 终篇
11.10日. 我们TSOI再次来到了熟悉的燕山大学,只不过这次是真刀真枪的干了. 望着那座熟悉的小桥,身边的好友不知此行过后还有多少. 下午才到,出人意外的是这次没有住燕大宾馆而是选择了熟悉的格林豪 ...
- CF1051D Bicolorings dp
水题一道 $f[i][j][S]$表示$2 * i$的矩形,有$j$个联通块,某尾状态为$S$ 然后转移就行了... #include <vector> #include <cstd ...