使用线程局部存储TLS

来源:互联网 发布:淘宝5xl的裤子是多少码 编辑:程序博客网 时间:2024/04/27 21:53

Thread local storage(TLS)统一进程的多个线程可以通过由TlsAlloc方法返回的索引值在线程自身的空间内存储和取回一个值。在以下这个例子里,索引值在进程开始时创建,当各个线程启动时,会各自申请一块动态内存并且将内存指针通过TlsSetValue方法存储到各自的TLS空间中(由先前的索引值标定)。CommonFunc方法使用TlsGetValue方法通过索引取得数据指针。在各个线程结束前,释放动态内存块。在进程结束见,调用TlsFree方法释放索引。

1#include <windows.h>
2#include <stdio.h>
3
4#define THREADCOUNT 4
5DWORD dwTlsIndex;
6
7VOID ErrorExit(LPSTR);
8
9VOID CommonFunc(VOID)
10{
11    LPVOID lpvData;
12
13// Retrieve a data pointer for the current thread.
14
15    lpvData = TlsGetValue(dwTlsIndex);
16   if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS))
17       ErrorExit("TlsGetValue error");
18
19// Use the data stored for the current thread.
20
21    printf("common: thread %d: lpvData=%lx/n",
22       GetCurrentThreadId(), lpvData);
23
24    Sleep(5000);
25}

26
27DWORD WINAPI ThreadFunc(VOID)
28{
29    LPVOID lpvData;
30
31// Initialize the TLS index for this thread.
32
33    lpvData = (LPVOID) LocalAlloc(LPTR, 256);
34   if (! TlsSetValue(dwTlsIndex, lpvData))
35       ErrorExit("TlsSetValue error");
36
37    printf("thread %d: lpvData=%lx/n", GetCurrentThreadId(), lpvData);
38
39    CommonFunc();
40
41// Release the dynamic memory before the thread returns.
42
43    lpvData = TlsGetValue(dwTlsIndex);
44   if (lpvData != 0)
45       LocalFree((HLOCAL) lpvData);
46
47   return 0;
48}

49
50int main(VOID)
51{
52    DWORD IDThread;
53    HANDLE hThread[THREADCOUNT];
54   int i;
55
56// Allocate a TLS index.
57
58   if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
59       ErrorExit("TlsAlloc failed");
60
61// Create multiple threads.
62
63   for (i = 0; i < THREADCOUNT; i++)
64   {
65       hThread[i] = CreateThread(NULL, // default security attributes
66         0,                           // use default stack size
67          (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function
68          NULL,                    // no thread function argument
69         0,                       // use default creation flags
70         &IDThread);              // returns thread identifier
71
72   // Check the return value for success.
73      if (hThread[i] == NULL)
74          ErrorExit("CreateThread error/n");
75    }

76
77   for (i = 0; i < THREADCOUNT; i++)
78       WaitForSingleObject(hThread[i], INFINITE);
79
80    TlsFree(dwTlsIndex);
81
82   return 0;
83}

84
85VOID ErrorExit (LPSTR lpszMessage)
86{
87    fprintf(stderr, "%s/n", lpszMessage);
88    ExitProcess(0);
89}

90

常用情景:
各个线程所处理的对象有所不同,但是所需要的处理却可能类似,例如多个线程同时处理多个文件,就可以将文件句柄存在在相应的Tls中,在使用相同的接口进行处理
背景知识:
每个线程除了共享进程的资源外还拥有各自的私有资源:一个寄存器组(或者说是线程上下文);一个专属的堆栈;一个专属的消息队列;一个专属的Thread Local Storage(TLS);一个专属的结构化异常处理串链。
TLS 是一个良好的Win32 特质,让多线程程序设计更容易一些。TLS是一个机制,经由它,程序可以拥有全域变量,但在不同的线程里有不同的值。也就是说,进程中的所有线程都可以拥有全域变量,但这些变量其实是特定对某个线程才有意义。例如,你可能有一个多线程程序,每一个线程都对不同的文件写文件(也因此它们使用不同的文件handle)。这种情况下,把每一个线程所使用的文件handle 储存在TLS 中,将会十分方便。当线程需要知道所使用的handle,它可以从TLS获得。重点在于:线程用来取得文件handle 的那一段码在任何情况下都是相同的,而从TLS中取出的文件handle却各不相同。非常灵巧,不是吗?有全域变数的便利,却又分属各线程。

 

 虽然TLS 很方便,它并不是毫无限制。在Windows NT 和Windows 95 之中,有64 个DWORD slots供每一个线程使用。这意思是一个进程最多可以有64 个「对各线程有不同意义」的DWORDs。 虽然TLS可以存放单一数值如文件handle,更常的用途是放置指针,指向线程的私有资料。有许多情况,多线程程序需要储存一堆数据,而它们又都是与各线程相关。许多程序员对此的作法是把这些变量包装为C 结构,然后把结构指针储存在TLS中。当新的线程诞生,程序就配置一些内存给该结构使用,并且把指针储存在为线程保留下来的TLS中。一旦线程结束,程序代码就释放所有配置来的区块。既然每一个线程都有64 个slots用来储存线程自己的数据,那么这些空间到底打哪儿来?在线程的学习中我们可以从结构TDB中看到,每一个thread database 都有64个DWORDs 给TLS 使用。当你以TLS 函式设定或取出数据,事实上你真正面对的就是那64DWORDs。好,现在我们知道了原来那些“对各线程有不同意义的全局变量”是存放在线程各自的TDB中阿。

接下来你也许会问:我怎么存取这64个DWORDS呢?我又怎么知道哪个DWORDS被占用了,哪个没有被占用呢?首先我们要理解这样一个事实:系统之所以给我们提供TLS这一功能,就是为了方便的实现“对各线程有不同意义的全局变量”这一功能;既然要达到“全局变量”的效果,那么也就是说每个线程都要用到这个变量,既然这样那么我们就不需要对每个线程的那64个DWORDS的占用情况分别标记了,因为那64个DWORDS中的某一个一旦占用,是所有线程的那个DWORD都被占用了,于是KERNEL32使用两个DWORDs(总共64 个位)来记录哪一个slot 是可用的、哪一个slot 已经被用。这两个DWORDs 可想象成为一个64位数组,如果某个位设立,就表示它对应的TLS slot 已被使用。这64 位TLS slot 数组存放在process database中(在进程一节中的PDB结构中我们列出了那两个DWORDs)。

下面的四个函数就是对TLS进行操作的:

(1)TlsAlloc

上面我们说过了KERNEL32 使用两个DWORDs(总共64 个位)来记录哪一个slot 是可用的、哪一个slot 已经被用。当你需要使用一个TLS slot 的时候,你就可以用这个函数将相应的TLS slot位置1。

(2)TlsSetValue

TlsSetValue 可以把数据放入先前配置到的TLS slot 中。两个参数分别是TLS slot索引值以及欲写入的数据内容。TlsSetValue 就把你指定的数据放入64 DWORDs 所组成的数组(位于目前的threaddatabase)的适当位置中。

(3)TlsGetValue

这个函数几乎是TlsSetValue 的一面镜子,最大的差异是它取出数据而非设定数据。和TlsSetValue一样,这个函数也是先检查TLS 索引值合法与否。如果是,TlsGetValue 就使用这个索引值找到64 DWORDs 数组(位于threaddatabase 中)的对应数据项,并将其内容传回。

(4)TlsFree

这个函数将TlsAlloc 和TlsSetValue 的努力全部抹消掉。TlsFree先检验你交给它的索引值是否的确被配置过。如果是,它将对应的64 位TLS slots位关闭。然后,为了避免那个已经不再合法的内容被使用,TlsFree 巡访进程中的每一个线程,把0 放到刚刚被释放的那个TLS slot上头。于是呢,如果有某个TLS 索引后来又被重新配置,所有用到该索引的线程就保证会取回一个0 值,除非它们再调用TlsSetValue。

原创粉丝点击