線程本地存儲 (TLS) 是一個方法,通過該方法,給定的多線程進程中的每個線程都可以分配存儲線程特定數(shù)據(jù)的位置。通過 TLS API(TlsAlloc、TlsGetValue、TlsSetValue、TlsFree)方式支持動態(tài)綁定(運行時)的線程特定數(shù)據(jù)。除了現(xiàn)有的 API 實現(xiàn),Win32 和 Visual C++ 編譯器現(xiàn)在還支持靜態(tài)綁定(加載時間)基于線程的數(shù)據(jù)。
TLS 的 API 實現(xiàn)
通過 Win32 API 層和編譯器實現(xiàn)“線程本地存儲”。有關(guān)詳細信息,請參見 Win32 API 文檔中的 TlsAlloc、TlsGetValue、TlsSetValue 和 TlsFree。
Visual C++ 編譯器包括使線程本地存儲操作更加自動化的關(guān)鍵字,而不是通過 API 層。將在下一節(jié)(TLS 的編譯器實現(xiàn))描述此語法。
TLS 的編譯器實現(xiàn)
為了支持 TLS,已將新屬性 thread 添加到了 C 和 C++ 語言,并由 Visual C++ 編譯器支持。此屬性是一個擴展存儲類修飾符,如上一節(jié)中所述。使用 __declspec 關(guān)鍵字聲明 thread 變量。例如,以下代碼聲明了一個整數(shù)線程局部變量,并用一個值對其進行初始化:
__declspec( thread ) int tls_i = 1;
TLS 的規(guī)則和限制
聲明靜態(tài)綁定線程的本地對象和變量時必須遵守下列原則:
- thread 屬性只能應(yīng)用于數(shù)據(jù)聲明和定義。它不能用于函數(shù)聲明或定義。例如,以下代碼將生成一個編譯器錯誤:
#define Thread __declspec( thread )
Thread void func(); // This will generate an error.
- 只能在具有 static 作用域的數(shù)據(jù)項上指定 thread 修飾符。包括全局數(shù)據(jù)對象(包括 static 和 extern)、本地靜態(tài)對象和 C++ 類的靜態(tài)數(shù)據(jù)成員。不可以用 thread 屬性聲明自動數(shù)據(jù)對象。以下代碼將生成編譯器錯誤:
#define Thread __declspec( thread )
void func1()
{
Thread int tls_i; // This will generate an error.
}
int func2( Thread int tls_i ) // This will generate an error.
{
return tls_i;
} - 線程本地對象的聲明和定義必須全都指定 thread 屬性。例如,以下代碼將生成錯誤:
#define Thread __declspec( thread )
extern int tls_i; // This will generate an error, since the
int Thread tls_i; // declaration and definition differ.
- thread 屬性不能用作類型修飾符。例如,以下代碼將生成一個編譯器錯誤:
char __declspec( thread ) *ch; // Error
- C++ 類不能使用 thread 屬性。但是,可以使用 thread 屬性將 C++ 類對象實例化。例如,以下代碼將生成一個編譯器錯誤:
#define Thread __declspec( thread )
class Thread C // Error: classes cannot be declared Thread.
{
// Code
};
C CObject;因為允許使用 thread 屬性的 C++ 對象的聲明,因此下面兩個示例在語義上是等效的:
#define Thread __declspec( thread )
Thread class B
{
// Code
} BObject; // OK--BObject is declared thread local.
class B
{
// Code
};
Thread B BObject; // OK--BObject is declared thread local. - 不將線程本地對象的地址視為常數(shù),并且涉及此類地址的任何表達式都不視為常數(shù)。在標準 C 中,這種作法的效果是禁止將線程本地變量的地址用作對象或指針的初始值設(shè)定項。例如,C 編譯器將以下代碼標記為錯誤:
#define Thread __declspec( thread )
Thread int tls_i;
int *p = &tls_i; //This will generate an error in C.
但是,此限制不適用于 C++。因為 C++ 允許動態(tài)初始化所有對象,因此可以用使用線程本地變量地址的表達式初始化對象。實現(xiàn)此操作的方式與實現(xiàn)線程本地對象結(jié)構(gòu)的方式相同。例如,以上顯示的代碼在作為 C++ 源文件編譯時不會生成錯誤。請注意:只有在其中獲取地址的線程仍然存在的情況下,線程本地變量的地址才有效。
- 標準 C 允許使用涉及引用自身的表達式初始化對象或變量,但只適用于非靜態(tài)作用域的對象。雖然 C++ 通常允許使用涉及引用自身的表達式動態(tài)初始化對象,但是這種類型的初始化不允許用于線程本地對象。例如:
#define Thread __declspec( thread )
Thread int tls_i = tls_i; // Error in C and C++
int j = j; // OK in C++, error in C
Thread int tls_i = sizeof( tls_i ) // Legal in C and C++
請注意:包含正在初始化的對象的 sizeof 表達式不建立對自身的引用且在 C 和 C++ 中都是合法的。
C++ 不允許此類對線程數(shù)據(jù)的動態(tài)初始化,因為將來可能要對線程本地存儲功能進行增強。
- 如果 DLL 將任何非本地數(shù)據(jù)或?qū)ο舐暶鳛?__declspec(線程),動態(tài)加載該 DLL 時會導(dǎo)致保護錯誤。使用 LoadLibrary 加載所有 DLL 后,每當代碼引用非本地 __declspec(線程)數(shù)據(jù)時,將導(dǎo)致系統(tǒng)故障。由于線程的全局變量空間是在運行時分配的,因此此空間的大小是以應(yīng)用程序的需求和所有靜態(tài)鏈接的 DLL 的需求相加為基礎(chǔ)計算出來的。使用 LoadLibrary 時,無法擴展此空間以允許放置用 __declspec(線程)聲明的線程本地變量。如果 DLL 可能是用 LoadLibrary 加載的,請在 DLL 中使用 TLS API(如 TlsAlloc)來分配 TLS。