您的位置:首页 > 编程语言

Windows核心编程【21】小结

2012-06-12 11:00 239 查看
第21章 线程局部存储区

有时将数据与一个对象的实例关联起来是有帮助的。我们可以使用线程局部存储区(Thread Local Storage,简称TLS)来将数据与一个正在执行的指定线程关联起来。例如,可以将创建线程的时间与线程关联起来,然后当线程终止的时候,就可以确定线程运行的时间长度。

(TLS可以实现“对各线程有不同意义的全局变量”)

C/C++运行库使用了TLS。由于C/C++运行库是在多线程应用程序出现的许多年前设计的,很多都是为单线程应用程序设计的。重要数据保存在静态变量中,这在多线程同时调用的情况下,容易导致静态变量的覆盖,从而出错。为解决这个问题,C/C++运行库使用了TLS,为每个线程分配独立的字符串指针。

作为开发人员,应该最大限度地减少对全局变量或静态变量的使用,并更多地依赖于自动变量(栈上的变量)和通过函数参数传入的数据。这是好事,因为栈上的变量始终都是与某个特定的线程相关联的。

TLS在创建DLL的时候更加有用,因为DLL通常并不知道它们被链接到的应用程序的结构是什么样的。

一、动态TLS

图21-1用来管理TLS的内部数据结构



系统中的每个进程都有一组正在使用标志(in-use flag),如上所示。每个标志可以被设为FREE或INUSE,表示该TLS元素是否正在使用。MS保证至少有TLS_MINIMUM_AVALIABLE(在WinNT.h中定义为64,系统会在需要的时候分配更多的TLS元素,最多可达1000多个。)个位表示可供使用。

先调用TlsAlloc,让系统对进程中的位标识进程检索并找到一个FREE标志。然后系统会将该标志从FREE改为INUSE并让TlsAlloc返回该标志在位数组中的索引。一个DLL(或应用程序)通常将这个索引保存在一个全局变量中。(因为这个值会在整个进程范围内使用,而不是在线程范围内使用)如果无法找到一个FREE标志,返回TLS_OUT_OF_INDEXES(在WinBase.h中定义为0xfffffff)。

当系统创建一个线程的时候,会分配TLS_MINIMUM_AVAILABLE个PVOID值,将它们都初始化为0,并与线程关联起来。如上图所示,每个线程都有自己的PVOID数组,数组中的每个PVOID可以保存任意值。

在能够将信息保存到线程的PVOID数组之前,必须知道数组中的哪个索引可供使用——正是前面调用TlsAlloc的目的。为了把一个值放到线程的数组中,应该调用TlsSetValue函数。http://msdn.microsoft.com/en-us/library/windows/desktop/ms686818(v=vs.85).aspx

BOOL WINAPI TlsSetValue(
__in      DWORD dwTlsIndex,
__in_opt  LPVOID lpTlsValue
);


这个函数把pvTlsValue参数所标识的一个PVOID值放到线程的数组中,参数dwTlsIndex标识一个索引值,表示在数组中的具体位置(前面通过TlsAlloc调用返回的值)。调用成功则返回TRUE。

相应的TlsGetValue获得值。64个TLS不能说是用之不竭的。应用程序可能会加载很多DLL,每个DL要几个就没了。一般情况下,都是动态申请一个结构(把数据封装起来),然后TLS存放指针。

不需要一个已经预定的TLS元素事,使用TlsFree。进程会将进程内的位标识数组中对应的INUSE标志重新设回FREE,此外,还会将所有线程中该元素的内存设为0。试图释放一个尚未分配的TLS元素将导致错误。

TlsAlloc会在返回之前,根据新分配的索引,在每个线程的数组中把对应的元素设为0。

(TlsAlloc是向进程申请一个位标识,然后可以使用该位标识来“私有化”各个线程。线程不能直接读取其余线程的数组。)

二、静态TLS

静态TLS使用的时候不必在代码中调用任何函数,因而更容易使用。

__declspec(thread)前缀是MS为VC编译器增加的一个修饰符,告诉编译器在可执行文件或DLL文件中,把对应的变量放到它自己的段中。__declspec(thread)后面的变量必须被声明为全局变量或静态变量(既可以在函数内,也可以在函数外)。

当编译器对剩下进行编译的时候,会将所有TLS变量放到它们自己的段中,这个段名为.tls。链接器会将所有对象模块中的.tls段合并成为一个大的.tls段,并将它保存到生成的可执行文件或DLL文件中。

为了让TLS能够正常工作,OS也必须参与进来。当系统将应用程序加载到内存的时候,会查看可执行文件中的.tls段,并分配一块足够大的内存来保存所有的静态TLS变量。每当应用程序中的代码引用到这些变量之一时,相应的引用会被解析到刚分配的这块内存中的一个位置。因此,编译器必须生成额外的代码来引用静态TLS变量,这使得应用程序不仅变得更大,而且只需起来也更慢。在x86CPU上,每次引用一个静态TLS变量会生成三条额外的机器指令。

如果进程创建了另一个线程,那么系统会获知这一情况并自动分配另一块内存来保存新线程的静态TLS变量。新线程只能访问自己的静态TLS变量,无法访问任何其他线程的TLS变量。

如果应用程序调用LoadLibrary来链接一个DLL,而且该DLL保护了静态TLS变量,这时会发生什么情况。为了给新DLL提供它需要的额外TLS内存,系统必须查看进程中所有已有的线程,并扩大它们的TLS内存块。另外,如果应用程序调用FreeLibrary来释放一个DLL,而且该DLL包含了静态TLS变量,那么与进程中的每个线程相关联的内存块也应该相应地缩减。好消息是,Vista对此提供了完全支持。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: