《Windows核心编程》读书笔记二十一章 线程局部存储区

本文是《Windows核心编程》第二十一章的读书笔记,主要探讨线程局部存储区(TLS)。动态TLS允许在创建DLL时关联数据与线程,避免全局变量的使用,确保线程安全性。C/C++运行库利用TLS为每个线程提供独立变量存储空间。
摘要由CSDN通过智能技术生成

第二十一章 线程局部存储区


本章内容

21.1 动态TLS

21.2 静态TLS


有时候将数据与一个对象的实力关联起来是有帮助的。比如SetWindowWord  SetWindowLong函数将数据和窗口关联起来。

可以使用线程局部存储区(Thread Local Storage TLS)来将数据与一个正在执行的指定线程关联起来。


c/c++运行库使用了TLS. C++运行库为每个线程分配独立的存储空间来存储特定的变量。

作者建议尽量避免使用全局变量。如果应用程序使用了全局变量和静态变量,应该对每个变量进行分析,并研究将其改成栈或堆上的变量。


也可以使用TLS技术--动态TLS和静态TLS,在创建DLL的时候更有用。因为DLL本身并不知道它被连接到的应用程序结构是什么样的。


21.1 动态TLS

应用程序通过调用一组4个函数来使用动态TLS。 以下是TLS的内部数据结构:

系统中每个进程都有一组正在使用的标志(in-use flag)每个标志可以设为 FREEINUSE,表示该TLS元素是否正在使用。MS保证至少有 TLS_MINIMUM_AVAILABLE个标志可供使用。 TLS_MINIMUM_AVAILABLE在Winnt.h中被定义为64,实际上最多可达1000多个。

首先需要使用TlsAlloc()
_Must_inspect_result_
WINBASEAPI
DWORD
WINAPI
TlsAlloc(
    VOID
    );
该函数让系统对进程中的位标志进行检索并找到一个 FREE标志。然后系统将该标志从 FREE改为 INUSE。并让 TlsAlloc返回该标志在数组中的索引。通常在DLL中这个索引保存在一个全局变量中。

如果 TlsAlloc无法在列表中找到一个 FREE标志,那么它会返回 TLS_OUT_OF_INDEXES(在winbase.h被定义为0xFFFFFFFF)

TlsAlloc第一次被调用的时候,系统会发现第一个标志为 FREE,于是将该标志改为 INUSE,并让 TlsAlloc返回0(第一条索引)


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

为了将信息保存到线程的PVOID数组之前,需要知道哪个索引可以使用(调用 TlsAlloc获得))
为了把一个值放到线程的数组中,使用 TlsSetValue函数
WINBASEAPI
BOOL
WINAPI
TlsSetValue(
    _In_ DWORD dwTlsIndex,
    _In_opt_ LPVOID lpTlsValue
    );



函数把dwTlsValue所标识的PVOID值放入线程的数组中,。dwTlsIndex是一个索引值,标识在数组中的具体位置。如果调用成功 TlsSetValue函数返回TRUE

一个线程调用 TlsSetValue的时候,会修改自己的数组。但其无法修改另一个线程的Tls值。如果要将一个线程的数据保存到另外一个线程上去。就是在创建线程的时候传一个值给他们。

在使用 TlsSetValue时应该总是传入前面调用 TlsAlloc时所返回的索引。
为了从线程数组取回一个值,使用 TlsGetValue
WINBASEAPI
LPVOID
WINAPI
TlsGetValue(
    _In_ DWORD dwTlsIndex
    );
函数会返回在索引为dwTlsIndex的TLS元素中保存的值。

当我们不在需要一个已经预定的Tls元素时,使用 TlsFree
WINBASEAPI
BOOL
WINAPI
TlsFree(
    _In_ DWORD dwTlsIndex
    );


使用动态TLS

如果DLL要使用TLS,在DllMain函数处理 DLL_PROCESS_ATTACH的时候调用 TlsAlloc,并在 DLL_PROCESS_DETACH的时候调用 TlsFree
TlsSetValueTlsGetValue的调用最可能发生在DLL提供的其他函数中


Tls的原则是需要使用时添加。例如一下代码
DWORD g_dwTlsIndex; // Assume that this is initialized
					// with the result of a call to TlsAlloc.

//..

void MyFunction(PSOMESTRUCT pSomeStruct) {
	if (pSomeStruct != NULL) {
		// The caller is priming this function.

		// see if we already allocated space to save the data.
		if (TlsGetValue(g_dwTlsIndex) == NULL) {
			// Space was never allocated. this is the first
			// time this function has ever been called by this thread.
			TlsSetValue(g_dwTlsIndex,
				HeapAlloc(GetProcessHeap(), 0, sizeof(*pSomeStruct)));
		}

		// Memory already exists for the data;
		// save the newly passed value.
		memcpy(TlsGetValue(g_dwTlsIndex), pSomeStruct, sizeof(*pSomeStruct));
	}
	else {
		// the caller already primed the function. Now it
		// wants to do something with the saved data.

		// Get the address of the saved data.
		pSomeStruct = (PSOMESTRUCT)TlsGetValue(g_dwTlsIndex);

		// The saved data is pointed to by pSomeStruct; use it.
	}
}

为了节省Tls的资源建议把Tls数据定义成结构体在Tls中保存结构体指针,这样新增数据仅仅修改结构体而不需要增加Tls索引的使用量。

再看以下代码。
	DWORD dwTlsIndex;
	PVOID pvSomeValue;
	//...

	dwTlsIndex = TlsAlloc();
	TlsSetValue(dwTlsIndex, (PVOID)12345);
	TlsFree(dwTlsIndex);

	// Assume that the dwTlsIndex value returned from
	// this call to TlsAlloc is identical to the index
	// returned by the earlier call to TlsAlloc.
	dwTlsIndex = TlsAlloc();

	pvSomeValue = TlsGetValue(dwTlsIndex);

执行结果,最后pvSomeValue的值是0

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

21.2 静态TLS
__declspec(thread) DWORD gt_dwStartTime = 0;

__declspec(thread)所修饰的变量必须是全局或静态变量。
编译的时候,会把所有TLS变量放到一个叫.tls的段里。
为了让TLS能够正常工作,操作系统也必须参与进来。系统将程序载入到内存时,会查看可执行文件的.tls段。并分配一块足够大的内存来包含所有静态TLS变量。
编译器必须生成额外代码来引用TLS变量,使得应用程序更大,执行效率也慢。

如果DLL中存在.tls段,应用程序加载DLL的时候会计算自身和dll共同的.tls段的大小并相加以后分配一块足够大的内存来包含所有隐式链接的DLL需要的TLS变量。

如果DLL是显示连接的,也包含TLS变量。系统必须查看进程中的所有已有的线程,并扩大他们的TLS内存块。另外如果应用程序调用FreeLibrary来释放一个DLL,而该DLL包含了静态TLS变量。那么进程中每个线程相关的内存块也应该相应的缩减。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值