Thread local storage (TLS)統(tǒng)一進程的多個線程可以通過由TlsAlloc方法返回的索引值在線程自身的空間內(nèi)存儲和取回一個值。在以下這個例子里,索引值在進程開始時創(chuàng)建,當各個線程啟動時,會各自申請一塊動態(tài)內(nèi)存并且將內(nèi)存指針通過TlsSetValue方法存儲到各自的TLS空間中(由先前的索引值標定)。CommonFunc方法使用TlsGetValue方法通過索引取得數(shù)據(jù)指針。在各個線程結束前,釋放動態(tài)內(nèi)存塊。在進程結束見,調(diào)用TlsFree方法釋放索引。
1
#include <windows.h>
2
#include <stdio.h>
3
4
#define THREADCOUNT 4
5
DWORD dwTlsIndex;
6
7
VOID ErrorExit(LPSTR);
8
9
VOID 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
27
DWORD 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
50
int 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
85
VOID ErrorExit (LPSTR lpszMessage)
86

{
87
fprintf(stderr, "%s\n", lpszMessage);
88
ExitProcess(0);
89
}
90
常用情景:
各個線程所處理的對象有所不同,但是所需要的處理卻可能類似,例如多個線程同時處理多個文件,就可以將文件句柄存在在相應的Tls中,在使用相同的接口進行處理
背景知識:
每個線程除了共享進程的資源外還擁有各自的私有資源:一個寄存器組(或者說是線程上下文);一個專屬的堆棧;一個專屬的消息隊列;一個專屬的Thread Local Storage(TLS);一個專屬的結構化異常處理串鏈。
TLS 是一個良好的Win32 特質(zhì),讓多線程程序設計更容易一些。TLS 是一個機制,經(jīng)由它,程序可以擁有全域變量,但在不同的線程里有不同的值。也就是說,進程中的所有線程都可以擁有全域變量,但這些變量其實是特定對某個線程才有意義。例如,你可能有一個多線程程序,每一個線程都對不同的文件寫文件(也因此它們使用不同的文件handle)。這種情況下,把每一個線程所使用的文件handle 儲存在TLS 中,將會十分方便。當線程需要知道所使用的handle,它可以從TLS 獲得。重點在于:線程用來取得文件handle 的那一段碼在任何情況下都是相同的,而從TLS中取出的文件handle 卻各不相同。非常靈巧,不是嗎?有全域變數(shù)的便利,卻又分屬各線程。
雖然TLS 很方便,它并不是毫無限制。在Windows NT 和Windows 95 之中,有64 個DWORD slots 供每一個線程使用。這意思是一個進程最多可以有64 個「對各線程有不同意義」的DWORDs。 雖然TLS 可以存放單一數(shù)值如文件handle,更常的用途是放置指針,指向線程的私有資料。有許多情況,多線程程序需要儲存一堆數(shù)據(jù),而它們又都是與各線程相關。許多程序員對此的作法是把這些變量包裝為C 結構,然后把結構指針儲存在TLS 中。當新的線程誕生,程序就配置一些內(nèi)存給該結構使用,并且把指針儲存在為線程保留下來的TLS 中。一旦線程結束,程序代碼就釋放所有配置來的區(qū)塊。既然每一個線程都有64 個slots 用來儲存線程自己的數(shù)據(jù),那么這些空間到底打哪兒來?在線程的學習中我們可以從結構TDB中看到,每一個thread database 都有64 個DWORDs 給TLS 使用。當你以TLS 函式設定或取出數(shù)據(jù),事實上你真正面對的就是那64 DWORDs。好,現(xiàn)在我們知道了原來那些“對各線程有不同意義的全局變量”是存放在線程各自的TDB中阿。
接下來你也許會問:我怎么存取這64個DWORDS呢?我又怎么知道哪個DWORDS被占用了,哪個沒有被占用呢?首先我們要理解這樣一個事實:系統(tǒng)之所以給我們提供TLS這一功能,就是為了方便的實現(xiàn)“對各線程有不同意義的全局變量”這一功能;既然要達到“全局變量”的效果,那么也就是說每個線程都要用到這個變量,既然這樣那么我們就不需要對每個線程的那64個DWORDS的占用情況分別標記了,因為那64個DWORDS中的某一個一旦占用,是所有線程的那個DWORD都被占用了,于是KERNEL32 使用兩個DWORDs(總共64 個位)來記錄哪一個slot 是可用的、哪一個slot 已經(jīng)被用。這兩個DWORDs 可想象成為一個64 位數(shù)組,如果某個位設立,就表示它對應的TLS slot 已被使用。這64 位TLS slot 數(shù)組存放在process database 中(在進程一節(jié)中的PDB結構中我們列出了那兩個DWORDs)。
下面的四個函數(shù)就是對TLS進行操作的:
(1)TlsAlloc
上面我們說過了KERNEL32 使用兩個DWORDs(總共64 個位)來記錄哪一個slot 是可用的、哪一個slot 已經(jīng)被用。當你需要使用一個TLS slot 的時候,你就可以用這個函數(shù)將相應的TLS slot位置1。
(2)TlsSetValue
TlsSetValue 可以把數(shù)據(jù)放入先前配置到的TLS slot 中。兩個參數(shù)分別是TLS slot 索引值以及欲寫入的數(shù)據(jù)內(nèi)容。TlsSetValue 就把你指定的數(shù)據(jù)放入64 DWORDs 所組成的數(shù)組(位于目前的thread database)的適當位置中。
(3)TlsGetValue
這個函數(shù)幾乎是TlsSetValue 的一面鏡子,最大的差異是它取出數(shù)據(jù)而非設定數(shù)據(jù)。和TlsSetValue 一樣,這個函數(shù)也是先檢查TLS 索引值合法與否。如果是,TlsGetValue 就使用這個索引值找到64 DWORDs 數(shù)組(位于thread database 中)的對應數(shù)據(jù)項,并將其內(nèi)容傳回。
(4)TlsFree
這個函數(shù)將TlsAlloc 和TlsSetValue 的努力全部抹消掉。TlsFree 先檢驗你交給它的索引值是否的確被配置過。如果是,它將對應的64 位TLS slots 位關閉。然后,為了避免那個已經(jīng)不再合法的內(nèi)容被使用,TlsFree 巡訪進程中的每一個線程,把0 放到剛剛被釋放的那個TLS slot 上頭。于是呢,如果有某個TLS 索引后來又被重新配置,所有用到該索引的線程就保證會取回一個0 值,除非它們再調(diào)用TlsSetValue。