線程局部存儲
線程局部存儲(英語:Thread-local storage,縮寫:TLS)是一種存儲持續期(storage duration),對象的存儲是在線程開始時分配,線程結束時回收,每個線程有該對象自己的實例。這種對象的鏈接性(linkage)可以是靜態的也可是外部的。
TLS的一個例子是用全局變量errno
表示錯誤號。這可能在多線程並發時產生同步錯誤。線程局部存儲的errno
是個解決辦法。
Windows的實現
編輯每個進程都有一組標誌,共TLS_MINIMUM_AVAILABLE(==64)個。每個標誌可以被設為FREE或INUSE,表示該TLS元素是否正在使用。注意這組標誌屬進程所有。當系統創建一個線程的時候,會為該線程分配與線程關聯的、屬於線程自己的PVOID型數組(共有TLS_MINIMUM_AVAILBALE個元素),數組中的每個PVOID可以保存任意值。
Windows API函數TlsAlloc
用於獲取進程中一個未用的TLS slot index。然後將該標誌從FREE改為INUSE,並返回該標誌在位數組中的索引,通常將該索引保存在一個全局變量中,因為這個值會在整個進程範圍內(而不是線程範圍內)使用。
調用TlsSetValue(dwTlsIndex,pvTlsValue)將一個PVOID值放到線程的數組中dwTlsIndex指定的具體位置。
函數TlsGetValue
與TlsSetValue
用於通過TLS slot index讀寫一個線程局部存儲變量所指向的內存塊。函數TlsFree
用於釋放TLS slot index。
在Win32線程信息塊的FS:[0x2C]地址處,存放的是線程局部存儲表的地址。[1]每個線程用它自己的線程局部存儲表的拷貝。TlsAlloc返回表中一個未使用的索引。因此每個線程可以用TlsSetValue(index)設置線程局部存儲值,用TlsGetValue(index)獲取線程局部存儲值。
Windows可執行程序也可以定義一個節(section),映射到進程每個線程的不同的內存分頁。這種節只定義在主程序里,動態鏈接庫(DLL)不應該包含這種節因為不會被LoadLibrary函數在加載時初始化。
對於Windows系統來說,全局變量或靜態變量會被放到".data"或".bss"段中,但當使用__declspec(thread)定義一個線程私有變量的時候,編譯器會把這些變量放到PE文件的".tls"段中。當系統啟動一個新的線程時,它會從進程的堆中分配一塊足夠大小的空間,然後把".tls"段中的內容複製到這塊空間中,於是每個線程都有自己獨立的一個".tls"副本。所以對於用__declspec(thread)定義的同一個變量,它們在不同線程中的地址都是不一樣的。對於一個TLS變量來說,它有可能是一個C++的全局對象,那麼每個線程在啟動時不僅僅是複製".tls"的內容那麼簡單,還需要把這些TLS對象初始化,必須逐個地調用它們的全局構造函數,而且當線程退出時,還要逐個地將它們析構,正如普通的全局對象在進程啟動和退出時都要構造、析構一樣。Windows PE文件的結構中有個叫數據目錄的結構。它總共有16個元素,其中有一元素下標為IMAGE_DIRECT_ENTRY_TLS,這個元素中保存的地址和長度就是TLS表(IMAGE_TLS_DIRECTORY結構)的地址和長度。TLS表中保存了所有TLS變量的構造函數和析構函數的地址,Windows系統就是根據TLS表中的內容,在每次線程啟動或退出時對TLS變量進行構造和析構。TLS表本身往往位於PE文件的".rdata"段中。
Pthreads的實現
編輯Pthreads API定義了線程特定的數據。
函數pthread_key_create
與pthread_key_delete
創建與刪除一個鍵,用於線程特定的數據。鍵的類型被稱為pthread_key_t
。鍵可以被所有線程看到。在每個線程,鍵可以用pthread_setspecific
函數關聯到線程特定的數據。數據可以隨後用pthread_getspecific
函數獲取。
特定於語言的實現
編輯C and C++
編輯C11的關鍵字_Thread_local
用於定義線程局部變量。在頭文件<threads.h>
定義了thread_local
為上述關鍵詞的同義。例如:
#include <threads.h>
thread_local int foo = 0;
C++11引入了thread_local
[2]關鍵字用於下述情形:
- 命名空間(全局)變量
- 文件靜態變量
- 函數靜態變量
- 靜態成員變量
此外,不同編譯器提供了各自的方法聲明線程局部變量:
- Solaris Studio C/C++, IBM XL C/C++,[3] GNU C,[4] Clang[5]與Intel C++ Compiler (Linux平台)[6]使用語法:
__thread int number;
- Visual C++,[7] Intel C/C++ (Windows systems),[8] C++Builder, 與Digital Mars C++ 使用語法:
__declspec(thread) int number;
- C++Builder也可以使用語法:
int __thread number;
Windows的版本早於Vista與Server 2008, __declspec(thread)
對於DLL只用於DLL被可執行程序綁定靜態加載,在LoadLibrary()函數動態加載DLL將報告protection fault或data corruption。[9]
Java
編輯Java語言中,線程局部變量使用ThreadLocal
類對象表示。ThreadLocal保持了變量的類型T,可以通過get/set方法訪問。例如,ThreadLocal保持了Integer值:
private static final ThreadLocal<Integer> myThreadLocalInteger = new ThreadLocal<Integer>();
Oracle/OpenJDK使用操作系統線程以避免性能代價。[10]
.NET 語言: C# 與Visual Basic.Net
編輯.NET Framework語言,靜態域可標記ThreadStatic attribute (頁面存檔備份,存於網際網路檔案館):
class FooBar {
[ThreadStatic] static int foo;
}
.NET 4.0,System.Threading.ThreadLocal<T> (頁面存檔備份,存於網際網路檔案館)可用於分配與惰性裝入線程局部變量。
class FooBar {
private static System.Threading.ThreadLocal<int> foo;
}
Also an API is available for dynamically allocating thread-local variables.
Python
編輯Python語言從版本2.4開始,threading模塊的local類可用於創建線程局部存儲:
import threading
mydata = threading.local()
mydata.x = 1
Ruby
編輯Ruby語言能創建/訪問線程局部變量使用[]=/[]方法:
Thread.current[:user_id] = 1
參考文獻
編輯- ^ Pietrek, Matt. Under the Hood. MSDN. May 2006 [6 April 2010]. (原始內容存檔於2016-03-03).
- ^ Section 3.7.2 in C++11 standard
- ^ IBM XL C/C++: Thread-local storage (頁面存檔備份,存於網際網路檔案館)
- ^ GCC 3.3.1: Thread-Local Storage (頁面存檔備份,存於網際網路檔案館)
- ^ Clang 2.0: release notes (頁面存檔備份,存於網際網路檔案館)
- ^ Intel C++ Compiler 8.1 (linux) release notes: Thread-local Storage (頁面存檔備份,存於網際網路檔案館)
- ^ Visual Studio 2003: Thread extended storage-class modifier (頁面存檔備份,存於網際網路檔案館)
- ^ Intel C++ Compiler 10.0 (Windows平台): Thread-local storage (頁面存檔備份,存於網際網路檔案館)
- ^ "Rules and Limitations for TLS". [2018-09-13]. (原始內容存檔於2008-04-04).
- ^ How is Java's ThreadLocal implemented under the hood?. Stack Overflow. Stack Exchange. [27 December 2015]. (原始內容存檔於2020-08-09).
外部連結
編輯- ELF Handling For Thread-Local Storage (頁面存檔備份,存於網際網路檔案館) — Document about an implementation in C or C++.
- ACE_TSS< TYPE > Class Template Reference
- RWTThreadLocal<Type> Class Template Documentation
- Article "Use thread-local Storage to Pass Thread Specific Data (頁面存檔備份,存於網際網路檔案館)" by Doug Doedens
- "Thread-Local Storage (頁面存檔備份,存於網際網路檔案館)" by Lawrence Crowl
- Article "It's Not Always Nice To Share (頁面存檔備份,存於網際網路檔案館)" by Walter Bright
- Practical ThreadLocal usage in Java: https://web.archive.org/web/20161220151503/https://www.captechconsulting.com/blogs/a-persistence-pattern-using-threadlocal-and-ejb-interceptors
- GCC "[1] (頁面存檔備份,存於網際網路檔案館)"