本文为线程本地存储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)的原理和实现——分类和原理的更多相关文章

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

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

  2. 线程局部存储TLS(thread local storage)

    同一全局变量或者静态变量每个线程访问的是同一变量,多个线程同时访存同一全局变量或者静态变量时会导致冲突,尤其是多个线程同时需要修改这一变量时,通过TLS机制,为每一个使用该全局变量的线程都提供一个变量 ...

  3. TLS Thread Local Storage

    https://blog.csdn.net/yusiguyuan/article/details/22938671 https://blog.csdn.net/simsunny22/article/d ...

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

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

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

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

  6. 线程本地存储(动态TLS和静态TLS)

    线程本地存储(TLS) 对于多线程应用程序,如果线程过于依赖全局变量和静态局部变量就会产生线程安全问题.也就是一个线程的使用全局变量可能会影响到其他也使用此全局变量的线程,有可能会造成一定的错误,这可 ...

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

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

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

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

  9. ThreadLocal(线程本地存储)

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

随机推荐

  1. 双缓冲解决控制台应用程序输出“闪屏”(C/C++,Windows)

    使用 C 语言编写游戏的小伙伴们想必起初都要遇到这样的问题,在不断清屏输出数据的过程中,控制台中的输出内容会不断地闪屏.出现这个问题的原因是程序对数据处理花掉的时间影响到了数据显示,或许你可以使用局部 ...

  2. Java-redis分布式锁 抢购秒杀系统 实现

    一.使用分布式锁要满足的几个条件: 系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现) 共享资源(各个系统访问同一个资源,资源的载体 ...

  3. 湖南大学ACM程序设计新生杯大赛(同步赛)L - Liao Han

    题目描述 Small koala special love LiaoHan (of course is very handsome boys), one day she saw N (N<1e1 ...

  4. CodeForces 797D Broken BST

    $dfs$,线段树. 通过观察可以发现,某位置要能被找到,和他到根这条路上的每个节点的权值存在密切的联系,且是父节点的左儿子还是右儿子也有联系. 可以从根开始$dfs$,边走边更新线段树,如果遍历左儿 ...

  5. 【记录】mysql 5.7.20安装 出现...mysql-5.7.20-winx64\data\is_writable’ Errcode: 2 - No such file or directory

    新到一家公司,安装mysql5.7.20时候出现一个问题(安装步骤可以参考这个): ...mysql-5.7.20-winx64\data\is_writable’ Errcode: 2 - No s ...

  6. 实验吧 recursive write up

    这是一个python写的文件 推测是用Freeze打包 所以必有一个Py_FrozenMain函数 分析函数有两个变量PYTHONINSPECT”和“PYTHONUNBUFFERED”之后都会调用ge ...

  7. 图形管线之旅 Part3

    原文:<A trip through the Graphics Pipeline 2011> 翻译:往昔之剑   转载请注明出处   此时,我们一路上通过多个驱动层和命令处理器将draw ...

  8. Scrapy爬取女神照片

    使用Scrapy趴一趴美女图 Scrapy,Python开发的一个快速,高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据.Scrapy用途广泛,可以用于数据挖掘.监测和自 ...

  9. BZOJ3669 NOI2014魔法森林

    按a从小到大排序,然后按b建图. 每次只需要找1~n中最大的b加当前的a计算答案即可. 这里还有一个小操作就是化边为点,把一条边的边权看做一个点的点权然后多连两条边. By:大奕哥 #include& ...

  10. 2017-2018-1 JAVA实验站 第三周作业

    2017-2018-1 JAVA实验站 第三周作业 团队展示 队名 JAVA实验站 拟作的团队项目描述 (2048)增加其他模式,使得2048更加丰富多彩 团队的首次合照 团队的特色描述 团队内部很团 ...