20.5 线程本地存储

来源:互联网 发布:元数据被拒绝 编辑:程序博客网 时间:2024/06/05 03:31

摘录于《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;

0 0
原创粉丝点击