2013-02-07 33 views
5

我發現MSDN中有關線程本地存儲初始值的矛盾。 This page說:Windows的線程本地存儲是否初始化值?

當創建線程時,系統爲TLS分配一個LPVOID值的數組,這些值初始化爲NULL。

這使我相信,如果我從一個從未爲同一索引調用過TlsSetValue的線程調用TlsGetValue,那麼我應該得到一個空指針。

This page,但是,說:

它是由程序員來保證......這個線程調用TlsGetValue前調用TlsSetValue。

這表明你不能依賴從TlsGetValue返回的值,除非你確定它已經用TlsSetValue明確初始化。

存儲在TLS時隙的數據可以具有0的值,因爲它仍然有它的初始值,或因爲該線程:

然而同時second page通過也說增強了初始化到空行爲所謂以0

所以我有兩個聲明說,該數據被初始化爲空(或0),一個說我必須讀值之前明確初始化它的TlsSetValue功能。在實驗上,這些值似乎被自動初始化爲空指針,但我無法知道我是否幸運,以及這是否總是如此。

我試圖避免使用DLL只是分配在DLL_THREAD_ATTACH。我想做一些懶惰的分配:

LPVOID pMyData = ::TlsGetValue(g_index); 
if (pMyData == nullptr) { 
    pMyData = /* some allocation and initialization*/; 
    // bail out if allocation or initialization failed 
    ::TlsSetValue(g_index, pMyData); 
} 
DoSomethingWith(pMyData); 

這是一個可靠和安全的模式?或者,在我嘗試閱讀之前,是否必須明確初始化每個線程中的插槽?

更新:該文件還說,TlsAlloc zeros out the slots for the allocated index。因此,以前是否有一個插槽先前被該程序的另一部分使用似乎無關緊要。

回答

10

說明系統爲TLS分配的初始值爲零時,文檔過於有用。聲明是真實的,但沒有用處。

原因是應用程序可以通過調用來釋放TLS插槽,因此當您分配一個插槽時,不能保證您是有史以來第一個獲得該插槽的人。因此,您不知道該值是系統分配的初始值0還是槽的前一個所有者分配的某個其他垃圾值。

考慮:

  • 組分A調用TlsAlloc和被分配時隙1時隙1從未使用過,所以它含有0.
  • 組分A調用TlsSetValue(1, someValue)其初始值。
  • 組件A調用TlsGetValue(1)並且它返回someValue
  • 組件A完成並調用TlsFree(1)
  • 組分B調用TlsAlloc和被分配時隙1
  • 組分B調用TlsGetValue(1)並得到someValue背面,因爲這是由A成分留下

因此垃圾值,它是由程序員確保在致電TlsGetValue之前,線程調用TlsSetValue。否則,你的TlsGetValue將讀取剩餘垃圾。

錯誤的文檔是說「剩餘垃圾的默認值爲零」,但這並沒有什麼幫助,因爲您不知道系統初始化和最終獲得給您的時間段之間發生了什麼。

阿德里安的後續促使我重新研究情況,確實內核零出槽時組件A調用TlsFree(1),這樣當組件B調用TlsGetValue(1)它得到零。這假定組件A中沒有錯誤,它在TlsFree(1)之後調用TlsSetValue(1)

+0

不錯的推理,但事實證明TlsAlloc會重新初始化插槽爲零,因此組件B將返回0而不是組件A留下的垃圾值。我已經添加了一個更詳細的答案。 –

+0

@Adrian它重新初始化當前線程的插槽。我相信其他線程仍然被擰緊。 [更新:其他線程確實沒有擰。他們在TlsFree時間被清零。] –

+0

感謝您的額外期待。我也玩過足夠的東西,看到他們重新初始化了TlsFree,但我還沒有檢查過其他線程。 –

1

我沒有看到矛盾。第二頁簡單地告訴您,系統將所有TLS插槽初始化爲0,但除了一些基本的邊界檢查之外,無法知道特定TLS索引是否包含有效數據(0可能是一個完全有效的用戶設置值也是!),並且由您來確定您請求的索引是您想要的索引並且它包含有效數據。

+0

「這是由程序員來確保......在調用TlsGetValue之前線程調用TlsSetValue」,這聽起來像是一個矛盾。 –

2

Raymond Chen的推理非常有意義,所以昨天我接受了他的答案,但今天我發現MSDN文檔中的另一個關鍵線TlsAlloc

如果函數成功,返回值是一個TLS索引。 索引的插槽已初始化爲零。

如果TlsAlloc確實初始化插槽爲零,那麼你不必擔心通過該插槽的老用戶留下的垃圾值。要驗證TlsAlloc不實際行爲這種方式,我跑瞭如下實驗:

void TlsExperiment() { 
    DWORD index1 = ::TlsAlloc(); 
    assert(index1 != TLS_OUT_OF_INDEXES); 
    LPVOID value1 = ::TlsGetValue(index1); 
    assert(value1 == 0); // Nobody else has used this slot yet. 
    value1 = reinterpret_cast<LPVOID>(0x1234ABCD); 
    ::TlsSetValue(index1, value1); 
    assert(value1 == ::TlsGetValue(index1)); 
    ::TlsFree(index1); 

    DWORD index2 = ::TlsAlloc(); 
    // There's nothing that requires TlsAlloc to give us back the recently freed slot, 
    // but it just so happens that it does, which is convenient for our experiment. 
    assert(index2 == index1); // If this assertion fails, the experiment is invalid. 

    LPVOID value2 = ::TlsGetValue(index2); 
    assert(value2 == 0); // If the TlsAlloc documentation is right, value2 == 0. 
         // If it's wrong, you'd expect value2 == 0x1234ABCD. 
} 

我跑在Windows 7上的實驗中,32位和64位的測試程序,用VS 2010

編譯

結果支持TlsAlloc將值重新初始化爲0的想法。我想TlsAlloc可能會做一些蹩腳的工作,例如爲當前線程清零值,但文檔明確指出「slots」(複數),所以它似乎安全的假設,如果你的線程還沒有使用一個插槽,那麼值將是0.

更新:進一步的實驗表明,它是TlsFree而不是TlsAlloc,可將插槽重新初始化爲0。因此,正如雷蒙德指出的那樣,在之後還有另一個組件被稱爲TlsSetValue 。這將是另一個組件中的錯誤,但會影響你的代碼。

相關問題