線程局部存儲

線程局部存儲（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地址處，存放的是線程局部存儲表的地址。每個線程用它自己的線程局部存儲表的拷貝。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
#include <threads.h>
thread_local int foo = 0;
```

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;`
- `int __thread number;`

Windows的版本早於Vista與Server 2008, `__declspec(thread)`對於DLL只用於DLL被可執行程序綁定靜態加載，在LoadLibrary函數動態加載DLL將報告protection fault或data corruption。

### Java

Java語言中，線程局部變量使用java/lang類對象表示。ThreadLocal保持了變量的類型T，可以通過get/set方法訪問。例如，ThreadLocal保持了Integer值：

```java
private static final ThreadLocal<Integer> myThreadLocalInteger = new ThreadLocal<Integer>();
```

Oracle/OpenJDK使用作業系統線程以避免性能代價。

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

.NET Framework語言，靜態域可標記 ThreadStatic attribute :

```csharp
class FooBar {
   [ThreadStatic] static int foo;
}
```

.NET 4.0， System.Threading.ThreadLocal 可用於分配與惰性裝入線程局部變量。

```csharp
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類可用於創建線程局部存儲：

```python
import threading
mydata = threading.local()
mydata.x = 1
```

### Ruby

Ruby語言能創建/訪問線程局部變量使用=/方法:

```ruby
Thread.current[:user_id] = 1
```

## 外部連結

- ELF Handling For Thread-Local Storage — Document about an implementation in C or C++.
- ACE_TSS Class Template Reference
- RWTThreadLocal 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 " "