執行緒與並行模型

執行緒區域儲存(thread-local storage)

想像一間開放式辦公室,每個人共用同樣的大型檔案櫃與文具櫃,但每個人另外有一個只有自己能打開的私人置物櫃。共用櫃裡的東西人人看得到;你置物櫃裡的東西只屬於你,即使置物櫃的設計人人相同。執行緒區域儲存就是執行緒的那個私人置物櫃:一個在程式碼裡看起來只有一個名字的變數,但每條執行緒都拿到自己一份獨立、私有的副本。

具體來說,執行緒預設共享行程的全域變數與堆積,這正是兩條執行緒寫同一個全域變數會相撞的原因。執行緒區域變數刻意打破那種共享:當一個變數被宣告為執行緒區域時,系統給每條執行緒自己一份獨立的實例,以該執行緒為鍵。執行緒 A 讀寫這個執行緒區域變數 x 時碰到的是 A 的副本;執行緒 B 碰到的是 B 的副本;它們從不互相干擾,即使原始碼指的是同一個 x。執行期會安排好這件事,依據當前在跑的是哪條執行緒,自動挑選正確的副本。

為什麼有用:有些狀態天生就是每執行緒專屬、絕不該共享的,例如一條執行緒自己的暫存緩衝區、它自己的亂數產生器種子、經典的 C 語言 errno(最後一次錯誤碼,必須是每執行緒專屬的,否則一條執行緒的錯誤會蓋掉另一條的),或一份每執行緒的快取。把這類狀態放進執行緒區域儲存,可以完全免去上鎖的需要,因為根本沒有共享要保護。誠實的提醒:執行緒區域儲存並非免費(每條執行緒都要付出自己那份副本的記憶體,執行緒一多就會累積),而且很容易被誤用成隱藏的全域變數,使程式碼更難讀懂、更難測試。它乾淨地解決了每執行緒專屬的情況,但當執行緒真的必須共享時,它幫不上忙。

C 語言的 errno 是執行緒區域的:當執行緒 A 的檔案讀取失敗而設定了 errno 時,執行緒 B 的 errno 毫髮無傷,所以每條執行緒都能檢查自己最後的錯誤,不會被另一條覆寫。若沒有執行緒區域儲存,errno 會是共享的全域變數,並行的錯誤回報就會亂成一團。

一個變數名稱,每條執行緒各有一份私有副本,避開了共享,也不需要鎖。

執行緒區域儲存之所以免去上鎖的需要,純粹是因為它免去了共享,當執行緒真的必須共享資料時它幫不上忙。把它當成隱藏的全域變數濫用,也會讓程式碼更難測試,而且每條執行緒的副本都要花記憶體。

又稱
TLSthread-specific dataper-thread storage執行緒專屬資料