摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P955
在一个多线程程序中,全局变量和所有分配的内存一样,是被程序的所有线程共享的。函数中的局部静态变量也被使用这个函数的所有线程共享。函数中的局部自动变量分配在每个线程自己的堆栈上,所以是线程私有的。(然而这种变量使用的内存在函数结束后接被释放了。)
在有的情况下,我们需要一种既是线程私有,又可以一直存在的存储单元,比如我们前面提到的 C 函数 strtok 就需要用到这种数据。标准 C 语言本身没有这种支持,但是 Windows 通过四个 API 函数提供了一种机制,让我们能够使用静态的线程私有变量。微软对 C 语言的扩展也支持这种机制。这就是线程本地存储。
下面我们演示如何使用线程本地存储的 API。
首先定义一个包含所有静态私有数据的结构,比如:
typedef struct
{
int a;
int b;
}
DATA, * PDATA;
然后,主线程调用 API 函数 TlsAlloc 以获取一个索引值:
dwTlsIndex = TlsAlloc();
这个索引值可以存储在一个全局变量中,或者通过参数结构被传递给线程函数。
线程函数一开始先为这个数据结构分配内存并调用 API 函数 TlsSetValue。函数 TlsSetValue 的调用需要用到我们前面获取的索引值 dwTlsIndex:
TlsSetValue(dwTlsIndex, GlobalAlloc(GPTR, sizeof(DATA));
这个函数将一个指针(指向一个线程本地数据结构)和一个特定的线程与特定的线程索引关联起来。现在,任何一个函数,包括原来的线程函数,可以用下面的代码来访问这个指针:
PDATA pdata;
...
pdata = (PDATA) TlsGetValue(dwTlsIndex);
现在,它可以设置或使用 pdata->a 和 pdata->b。在线程函数终止运行之前,它会释放已分配的内存:
GlobalFree (TlsGetValue(dwTlsIndex));
当所有使用这个本地存储结构的线程都结束后,我们需要在主线程释放其索引:
TlsFree (dwTlsIndex);
为了更好地理解线程本地存储的机制,让我们来分析分析这些函数是如何被实现的。(我自己并不确定 Windows 是怎么实现的,但是下面的描述应该比较接近。)首先,TlsAlloc 会分配一块内存(初始长度为 0 字节)并返回一个索引值,用作这块内存的指针。每当用该索引值调用 TlsSetValue 的时候,这块内存被重新分配以增加 8 个字节。这 8 个字节用以保存调用该函数的线程的 ID(可以通过调用函数 GetCurrentThreadId 得到)以及传给 TlsSetValue 函数的指针。TlsGetVal 使用线程 ID 搜索索引表并返回相应的指针。TlsFree 最终会释放 TlsAlloc 分配的内存。所以,这个实现机制并不复杂,我们自己实现也不困难。当然,既然 Windows 已经实现了,我们直接用就是了。
此外,微软还扩充了 C 语言以加入这个机制,使用起来更简单。只需要在每个线程需要区分的变量定义之前插入一个特定的前置符__declspec (thread)即可,比如要定义全局静态变量,可使用
__declspec (thread) int iGlobal = 1;
而要定义函数内部的静态变量,可使用
__declspec (thread) static int iLocal = 2;