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