dll中的tls

线程局部变量引入可以解决多线程修改静态或者全局变量的不同步问题。

举个简单的例子,一个函数fn可能需要修改一个静态变量,写这个函数的本意是自己线程内部调用,但是如果其它线程也可能调用这个函数的话,可能就破坏了这个静态变量的值。使用线程局部变量保证各个线程使用自己的一副拷贝,互不干扰。

   这里需要注意一些问题,关于tls。比如__declspec(thread)只能用于定义静态的或者全局的变量,不能作用于类,但是可以作用于类对象等等。特别需要注意的是dll中的线程局部变量。如果dll中用__declspec(thread)定义了一个线程局部变量,而该dll是被动态加载LoadLibarary,那么就会出现访问错误。因为线程局部变量是运行时创建的,而使用LoadLibrary加载进dll后是没有额外的空间(也不知道预留多少空间)来存放此线程局部变量的,因为会出现访问错误。

     如果你dll用__declspec(thread)来定义一个线程局部变量,那么需要确保你的dll是静态加载。如果你不能保证这点,你最好使用tls api来创建线程局部变量。

 

msdn关于LoadLibrary函数的介绍中最后由这么一段:

The Visual C++ compiler supports a syntax that enables you to declare thread-local variables: _declspec(thread). If you use this syntax in a DLL, you will not be able to load the DLL explicitly using LoadLibrary or LoadLibraryEx. If your DLL will be loaded explicitly, you must use the thread local storage functions instead of _declspec(thread).

 

与动态与动态T L S一样,静态T L S也能够将数据与线程联系起来。但是,静态T L S在代码中使用起来要容易得多,因为不必调用任何函数就能够使用它。

比如说,你想要将起始时间与应用程序创建的每个线程联系起来。只需要将起始时间变量声明为下面的形式:

 

__declspec(thread) DWORD gt_dwStartTime = 0;

__d e c l s p e c ( t h r e a d )的前缀是M i c r o s o f t添加给Visual C++编译器的一个修改符。它告诉编译器,对应的变量应该放入可执行文件或D L L文件中它的自己的节中。_ _ d e c l s p e c ( t h r e a d )后面的变量必须声明为函数中(或函数外)的一个全局变量或静态变量。不能声明一个类型为_ _d e c l s p e c ( t h r e a d )的局部变量。这不应该是个问题,因为局部变量总是与特定的线程相联系的。我将前缀g t _用于全局T L S变量,而将s t _用于静态T L S变量。

当编译器对程序进行编译时,它将所有的T L S变量放入它们自己的节,这个节的名字是. t l s。 链接程序将来自所有对象模块的所有. t l s节组合起来,形成结果的可执行文件或D L L文件中的 一个大的. t l s节。

为了使静态T L S能够运行,操作系统必须参与其操作。当你的应用程序加载到内存中时,系统要寻找你的可执行文件中的. t l s节,并且动态地分配一个足够大的内存块,以便存放所有的静态T L S变量。你的应用程序中的代码每次引用其中的一个变量时,就要转换为已分配内存块中包含的一个内存位置。因此,编译器必须生成一些辅助代码来引用该静态T L S变量,这将使你的应用程序变得比较大而且运行的速度比较慢。在x86 CPU上,将为每次引用的静态T L S变量生成3个辅助机器指令。

如果在进程中创建了另一个线程,那么系统就要将它捕获并且自动分配另一个内存块,以便存放新线程的静态T L S变量。新线程只拥有对它自己的静态T L S变量的访问权,不能访问属于其他线程的T L S变量。

这就是静态T L S变量如何运行的基本情况。现在让我们来看一看D L L的情况。你的应用程序很可能要使用静态T L S变量,并且链接到也想使用静态T L S变量的一个D L L。当系统加载你的应用程序时,它首先要确定应用程序的. t l s节的大小,并将这个值与你的应用程序链接的D L L中的任何. t l s节的大小相加。当在你的进程中创建线程时,系统自动分配足够大的内存块来存放应用程序需要的所有T L S变量和所有隐含链接的D L L。

下面让我们来看一下当应用程序调用L o a d L i b r a r y,以便链接到也包含静态T L S变量的一个D L L时,将会发生什么情况。系统必须查看进程中已经存在的所有线程,并扩大它们的T L S内存块,以便适应新D L L对内存的需求。另外,如果调用F r e e L i b r a r y来释放包含静态T L S变量的D L L,那么与进程中的每个线程相关的的内存块应该被压缩。

对于操作系统来说,这样的管理任务太重了。虽然系统允许包含静态T L S变量的库在运行期进行显式加载,但是T L S数据没有进行相应的初始化。如果试图访问这些数据,就可能导致访问违规。这是使用静态T L S的唯一不足之处。当使用动态T L S时,不会出现这个问题。使用动态T L S的库可以在运行期进行加载,并且可以在运行期释放,根本不会产生任何问题。

 

如果在dll中使用__declspec(thread)来定义一个静态线程局部变量,并且此dll被动态加载,那么对于此线程局部变量的访问会出现异常。不过这个定位起来还是比较容易,看看出错的地方是对于此线程局部变量的访问出错,然后转到汇编码,可以看到类似tls的字样,例如下面的汇编码:

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值