一、執行緒區域儲存(Windows, Thread Local Storage)
TLS 是一個機制,經過它,程式可以擁有全域變數,但處於“每一線程各不相同”的狀態。也就是說,進程中的所有線程都可以擁有全域變數,但這些變數其實是特定對某個線程才有意義,各個線程擁有全域變數的一個副本,各自之間不相影響。就是這麼一個意思,比如我定義了一個全域變數 int a=10,那麼我線上程1中對a進行操作a=a-1,如果我沒用TLS,那麼線程2開始獲得的a就是9。而如果採取了TLS,不管線程1中對a進行了如何的修改操作,其他的線程一開始獲得的a還是10,不會修改。這個全域的變數a是沒有儲存線上程堆棧中的,是在全域的堆棧中,但是卻被各個線程“共用”且互不影響
TLS的4個 API: TlsAlloc、TlsGetValue、TlsSetValue 和 TlsFree。
使用了TLS後,當有新的線程對象誕生,系統就會給該線程分配一個區塊,TLS中每一個線程的限制是64的DWORD。也就是你在各個線程之間最多“共用”64個全域DWORD的值,不過,這也是絕對夠用了。
一旦線程結束,程式碼就釋放所有配置來的區塊。我們可以從結構TDB中看到,每一個 thread database 都有64 個DWORDs 給TLS 使用。當你以TLS 函式設定或取出資料,事實上你真正面對的就是那 64 DWORDs。KERNEL32 使用兩個DWORDs(總共64 個位)隊列來記錄哪一個DWORD 是可用的、哪一個DWORD已經被用。這兩個DWORDs 可想象成為兩個DWORD數組,合起來供64 位元。
(1)TlsAlloc
上面我們說過了 KERNEL32 使用兩個DWORDs(總共64 個位)來記錄哪一個DWORD是可用的、哪一個DWORD 已經被用。當你需要使用一個 TLS slot 的時候,你就可以用這個函數將相應的TLS slot位置1。
(2)TlsSetValue
TlsSetValue 可以把資料放入先前配置到的TLS slot 中。兩個參數分別是TLS slot 索引值以及欲寫入的資料內容。TlsSetValue 就把你指定的資料放入64 DWORDs 所組成
的數組(位於目前的thread database)的適當位置中。
(3)TlsGetValue
這個函數幾乎是TlsSetValue 的一面鏡子,最大的差異是它取出資料而非設定資料。和TlsSetValue 一樣,這個函數也是先檢查TLS 索引值合法與否。如果是,TlsGetValue就使用這個索引值找到64 DWORDs 數組(位於 thread database 中)的對應資料項目,並將其內容傳回。
(4)TlsFree
這個函數將 TlsAlloc 和TlsSetValue 的努力全部抹消掉。TlsFree 先檢驗你交給它的索引值是否的確被配置過。如果是,它將對應的64 位元 TLS slots 位關閉。然後,為了避免那個已經不再合法的內容被使用,TlsFree 巡訪進程中的每一個線程,把0 放到剛剛被釋放的那個 TLS slot 上頭。於是呢,如果有某個TLS 索引後來又被重新設定,所有用到該索引的線程就保證會取回一個0 值,除非它們再調用TlsSetValue。
二、執行緒區域儲存(Linux, Thread Specific Data)
下面說一下線程儲存的具體用法。
(1)建立一個類型為 pthread_key_t 類型的變數。
(2)調用 pthread_key_create() 來建立該變數。該函數有兩個參數,第一個參數就是上面聲明的 pthread_key_t 變數,第二個參數是一個清理函數,用來線上程釋放該線程儲存的時候被調用。該函數指標可以設成 NULL ,這樣系統將調用預設的清理函數。
(3)當線程中需要儲存特殊值的時候,可以調用 pthread_setspcific() 。該函數有兩個參數,第一個為前面聲明的 pthread_key_t 變數,第二個為 void* 變數,這樣你可以儲存任何類型的值。
(4)如果需要取出所儲存的值,調用 pthread_getspecific() 。該函數的參數為前面提到的 pthread_key_t 變數,該函數返回 void * 類型的值。
下面是前面提到的函數的原型:
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
三、MSDN上的一個例子《Using Thread Local Storage》
#include <windows.h><br />#include <stdio.h> </p><p>#define THREADCOUNT 4<br />DWORD dwTlsIndex; </p><p>VOID ErrorExit(LPSTR); </p><p>VOID CommonFunc(VOID)<br />{<br /> LPVOID lpvData; </p><p>// Retrieve a data pointer for the current thread. </p><p> lpvData = TlsGetValue(dwTlsIndex);<br /> if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS))<br /> ErrorExit("TlsGetValue error"); </p><p>// Use the data stored for the current thread. </p><p> printf("common: thread %d: lpvData=%lx/n",<br /> GetCurrentThreadId(), lpvData); </p><p> Sleep(5000);<br />} </p><p>DWORD WINAPI ThreadFunc(VOID)<br />{<br /> LPVOID lpvData; </p><p>// Initialize the TLS index for this thread. </p><p> lpvData = (LPVOID) LocalAlloc(LPTR, 256);<br /> if (! TlsSetValue(dwTlsIndex, lpvData))<br /> ErrorExit("TlsSetValue error"); </p><p> printf("thread %d: lpvData=%lx/n", GetCurrentThreadId(), lpvData); </p><p> CommonFunc(); </p><p>// Release the dynamic memory before the thread returns. </p><p> lpvData = TlsGetValue(dwTlsIndex);<br /> if (lpvData != 0)<br /> LocalFree((HLOCAL) lpvData); </p><p> return 0;<br />} </p><p>int main(VOID)<br />{<br /> DWORD IDThread;<br /> HANDLE hThread[THREADCOUNT];<br /> int i; </p><p>// Allocate a TLS index. </p><p> if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)<br /> ErrorExit("TlsAlloc failed"); </p><p>// Create multiple threads. </p><p> for (i = 0; i < THREADCOUNT; i++)<br /> {<br /> hThread[i] = CreateThread(NULL, // default security attributes<br /> 0, // use default stack size<br /> (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function<br /> NULL, // no thread function argument<br /> 0, // use default creation flags<br /> &IDThread); // returns thread identifier </p><p> // Check the return value for success.<br /> if (hThread[i] == NULL)<br /> ErrorExit("CreateThread error/n");<br /> } </p><p> for (i = 0; i < THREADCOUNT; i++)<br /> WaitForSingleObject(hThread[i], INFINITE); </p><p> TlsFree(dwTlsIndex);<br /> return 0;<br />} </p><p>VOID ErrorExit (LPSTR lpszMessage)<br />{<br /> fprintf(stderr, "%s/n", lpszMessage);<br /> ExitProcess(0);<br />}<br />
四、MAN上的一個例子《Using Thread Specific Data》
#include <malloc.h><br />#include <pthread.h><br />#include <stdio.h><br />/* The key used to associate a log file pointer with each thread. */<br />static pthread_key_t thread_log_key;<br />/* Write MESSAGE to the log file for the current thread. */<br />void write_to_thread_log (const char* message)<br />{<br />FILE* thread_log = (FILE*) pthread_getspecific (thread_log_key);<br />fprintf (thread_log, “%s/n”, message);<br />}<br />/* Close the log file pointer THREAD_LOG. */<br />void close_thread_log (void* thread_log)<br />{<br />fclose ((FILE*) thread_log);<br />}<br />void* thread_function (void* args)<br />{<br />char thread_log_filename[20];<br />FILE* thread_log;<br />/* Generate the filename for this thread’s log file. */<br />sprintf (thread_log_filename, “thread%d.log”, (int) pthread_self ());<br />/* Open the log file. */<br />thread_log = fopen (thread_log_filename, “w”);<br />/* Store the file pointer in thread-specific data under thread_log_key. */<br />pthread_setspecific (thread_log_key, thread_log);<br />write_to_thread_log (“Thread starting.”);<br />/* Do work here... */<br />return NULL;<br />}<br />int main ()<br />{<br />int i;<br />pthread_t threads[5];<br />/* Create a key to associate thread log file pointers in<br />thread-specific data. Use close_thread_log to clean up the file<br />pointers. */<br />pthread_key_create (&thread_log_key, close_thread_log);<br />/* Create threads to do the work. */<br />for (i = 0; i < 5; ++i)<br />pthread_create (&(threads[i]), NULL, thread_function, NULL);<br />/* Wait for all threads to finish. */<br />for (i = 0; i < 5; ++i)<br />pthread_join (threads[i], NULL);<br />return 0;<br />}<br />
參考1
參考2