執行緒本地儲存

**執行緒局部儲存** (**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] 位址處，存放的是執行緒局部儲存表的位址。每個執行緒用它自己的執行緒局部儲存表的拷貝。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` 為上述關鍵詞的同義。例如：

C++11 引入了 `thread_local` 關鍵字用於下述情形：

* 命名空間 (全域) 變數
* 檔案靜態變數
* 函式靜態變數
* 靜態成員變數

此外，不同編譯器提供了各自的方法聲明執行緒局部變數：

* Solaris Studio C/C++, IBM XL C/C++, GNU C, Clang 與 Intel C++ Compiler (Linux 平台) 使用語法：

　
: `__thread int number;`
* Visual C++, Intel C/C++ (Windows systems), 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。

### Java

Java 語言中，執行緒局部變數使用 `ThreadLocal` 類物件表示。ThreadLocal 保持了變數的類型 T，可以通過 get/set 方法存取。例如，ThreadLocal 保持了 Integer 值：

Oracle/OpenJDK 使用作業系統執行緒以避免效能代價。

### .NET 語言: C# 與 Visual Basic.Net

.NET Framework 語言，靜態域可標記 ThreadStatic attribute （頁面存檔備份，存於網際網路檔案館）:

.NET 4.0，System.Threading.ThreadLocal<T> （頁面存檔備份，存於網際網路檔案館）可用於分配與惰性裝入執行緒局部變數。

Also an API
is available for dynamically allocating thread-local variables.

### Python

Python 語言從版本 2.4 開始，_threading_ 模組的 _local_ 類可用於建立執行緒局部儲存：

### Ruby

Ruby 語言能建立 / 存取執行緒局部變數使用 []=/[] 方法:

## 參考文獻

## 外部連結

* 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] （頁面存檔備份，存於網際網路檔案館）"