C++ 之線程局部存儲

 


線程局部存儲(TLS,thread local storage)是一個已有的概念。簡單地說,所謂線程局部存儲變量,就是擁有線程生命期及線程可見性的變量。

線程局部存儲實際上是由 單線程程序中的全局/靜態變量被應用到多線程程序中 被線程共享而來。

通常情況下,線程會擁有自己的棧空間,但是堆空間、靜態數據區(如果從可執行文件的角度來看,靜態數據區對應的是可執行文件的data、bss段的數據,而從C/C++語言層面而言,則對應的是全局/靜態變量)則是共享的。這樣一來,全局、靜態變量在這種多線程模型下就總是在線程間共享的。

全局、靜態變量的共享雖然會帶來一些好處,尤其對一些資源性的變量(比如文件句柄)來說也是應該的,不過並不是所有的全局、靜態變量都適合在多線程的情況下共享。

例如:代碼清單6-27所示的例子:

MaySetErr函數會根據輸入值input設置全局的錯誤碼errorCode。當用兩個線程運行該函數的時候,最終獲得的errorCode的值將是不確定的,或者說,將由系統如何調度兩個線程而決定。

實際上,本例中的errorCode即是POSIX標準中的全局變量錯誤碼errno在多線程情況下遭遇的問題的一個簡化。一旦errno在線程間共享,則一些程序中運行的錯誤將會被隱藏不報。而解決的辦法就是爲每個線程指派一個全局的errno,即TLS化的errno

各個編譯器公司都有自己的TLS標準。我們在g++/clang++/xlc++中可以看到如下的語法:

__thread int errCode;

即在全局或者靜態變量的聲明中加上關鍵字__thread,即可將變量聲明爲TLS變量。每個線程將擁有獨立的errCode的拷貝,一個線程中對errCode的讀寫並不會影響另外一個線程中的errCode的數據

 

C++11對TLS標準做出了一些統一的規定。與__thread修飾符類似,聲明一個TLS變量的語法很簡單,即通過thread_local修飾符聲明變量即可。

int thread_ local errCode;

 

一旦聲明一個變量爲thread_local,其值將在線程開始時被初始化,而在線程結束時,該值也將不再有效。對於thread_local變量地址取值(&),也只可以獲得當前線程中的TLS變量的地址值。


雖然TLS變量的聲明很簡單,使用也很直觀,不過實際上TLS的實現需要涉及編譯器、鏈接器、加載器甚至是操作系統的相互配合。在TLS中一個常被討論的問題就是TLS變量的靜態/動態分配的問題,即TLS變量的內存究竟是在程序一開始就被分配還是在線程開始運行時被分配。通常情況下,前者比後者更易於實現。     C++11標準允許平臺/編譯器自行選擇採用靜態分配或動態分配,或者兩者都支持。

 

還有一點值得注意的是,C++11對TLS只是做了語法上的統一,而對其實現並沒有做任何性能上的規定。這可能導致

thread_local聲明的變量在不同平臺或者不同的TLS實現上出現不同的性能(通常TLS變量的讀寫性能不會高於普通的全局/靜態變量)。如果讀者想得到最佳的平臺上的TLS變量的運行性能的話,最好還是閱讀代碼運行平臺的相關文檔。


---6.4chapter

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章