線程局部存儲(Thread Local Storage,TLS)主要用於在多線程中,存儲和維護一些線程相關的數據,存儲的數據會被關聯到當前線程中去,並不須要鎖來維護。。windows
所以也沒有多線程間資源競爭問題,那如何去實現TLS存儲呢,主要有如下幾種方式:api
__thread
修飾符__declspec(thread)
修飾符pthread_setspecific
和pthread_getspecific
接口TlsSetValue
和TlsGetValue
其中__thread和__declspec(thread)用起來最爲方便,只須要在static或者全局變量前加上此修飾符,而後在線程裏面訪問變量就好了安全
例如:多線程
tb_void_t tb_thread_func(tb_cpointer_t priv) { // 定義一個線程局部變量 static __thread int a = 0; // 初始化這個變量,設置爲當前線程id if (!a) a = tb_thread_self(); }
若是運行多個線程的話,上述代碼中每一個線程的變量a的值,都是不相同的,值爲每一個線程的id函數
而 __declspec(thread)
用起來也是相似,只需替換下 __thread
就好了工具
雖然這兩個修飾符用起來很方便,可是須要編譯器支持,雖然如今大部分平臺的編譯器都已支持,可是做爲跨平臺開發,這樣仍是不夠的測試
畢竟仍是有很多低版本gcc,不必定支持__thread
,尤爲是嵌入式開發領域,交叉編譯工具鏈中的編譯器支持力度差別仍是蠻大的。。線程
另外,使用__thread
進行tls數據維護,須要手動管理相關內存的釋放問題,用的很差很容易致使內存泄露。。code
pthread 的 tls 相關接口,比較完善,而且支持註冊free函數,在線程退出的時候,自動釋放相關tls數據,避免內存泄露,可是使用上稍顯複雜了些協程
咱們看個簡單的例子:
// 測試線程中tls變量存儲的key,需定義爲全局或者static static pthread_key_t g_local_key = 0; static tb_void_t tb_thread_local_free(tb_pointer_t priv) { tb_trace_i("thread[%lx]: free: %p", tb_thread_self(), priv); } static tb_void_t tb_thread_local_init(tb_void_t) { // 建立tls的key,而且設置自動釋放函數 pthread_key_create(&g_local_key, tb_thread_local_free); } static tb_int_t tb_thread_local_test(tb_cpointer_t priv) { // 在全部線程中,僅執行一次,用於在線程內部初始化 tls 的 key static pthread_once_t s_once = PTHREAD_ONCE_INIT; pthread_once(&s_once, tb_thread_local_init); // 嘗試讀取當前tls數據 tb_size_t local; if (!(local = (tb_size_t)pthread_getspecific(g_local_key))) { // 設置tls數據爲當前線程id tb_size_t self = tb_thread_self(); if (0 == pthread_setspecific(g_local_key, (tb_pointer_t)self)) local = self; } return 0; }
看上去複雜了些,可是更加靈活,若是不須要在線程內部建立key的話,就不須要調用pthread_once
了,直接把建立好的key傳入線程內部去訪問就好。
此套接口(TlsSetValue, TlsGetValue, TlsAlloc, TlsFree),屬於windows的tls操做接口,固然不能跨平臺了,使用起來和pthread的差很少,可是沒法註冊自動釋放函數,而且也沒提供相似pthread_once
的接口 在線程內部自建立key,功能上稍顯不足。。
static tb_int_t tb_thread_local_test(tb_cpointer_t priv) { // 建立一個tls的key,注:此處非線程安全,最好放到相似pthread_once提供的init函數中去建立 // 此處就臨時先這麼寫了,僅僅只是爲了方便描述api用法,不要照搬哦。。 static DWORD s_key = 0; if (!s_key) s_key = TlsAlloc(); // 嘗試讀取當前tls數據 DWORD local; if (!(local = TlsGetValue(s_key))) { // 設置tls數據爲當前線程id tb_size_t self = tb_thread_self(); if (TlsSetValue(s_key, (LPVOID)self)) local = self; } return 0; }
其實windows上還提供了FlsAlloc, FlsSetValue系列接口,給協程使用,而且支持註冊自動釋放的回調函數,不過對系統版本有些要求,像xp這些老系統就用不了了。。 這裏就很少描述了。
thread_local
接口封裝最近對tbox的tls接口進行了改造,而且重構了實現邏輯,在剪口易用性、功能性以及效率上都獲得了很大的提高。。
目前支持如下功能:
用起來也很方便,很pthread很相似,可是內部自動調用了pthread_once
,不用想pthread那樣顯式的去調用它了,例如:
static tb_void_t tb_demo_thread_local_free(tb_cpointer_t priv) { tb_trace_i("thread[%lx]: free: %p", tb_thread_self(), priv); } static tb_int_t tb_demo_thread_local_test(tb_cpointer_t priv) { /* 線程安全地初始化一個tls對象,至關於key,而且註冊自動free回調 * * 注:雖然全部線程都會執行到這個tb_thread_local_init * 可是s_local的tls對象,只會確保初始化一次,內部有相似pthread_once接口來維護 */ static tb_thread_local_t s_local = TB_THREAD_LOCAL_INIT; if (!tb_thread_local_init(&s_local, tb_demo_thread_local_free)) return -1; // 嘗試讀取當前tls數據 tb_size_t local; if (!(local = (tb_size_t)tb_thread_local_get(&s_local))) { // 設置tls數據爲當前線程id tb_size_t self = tb_thread_self(); if (tb_thread_local_set(&s_local, (tb_cpointer_t)self)) local = self; } return 0; }
在線程退出時,它會自動調用free回調,釋放對應殘留的tls數據,而且在tb_exit
退出後,銷燬全部建立的tls對象
固然你能夠能夠主動調用:tb_thread_local_exit(&s_local)
來銷燬它。。
tbox的這套接口,相比pthread減小了一個init的回調函數,比windows那套多了自動釋放的機制,而且同時支持跨平臺。。
以前我看到一些庫中,對__thread
和pthread接口進行了混用,感到非常莫名,我的感受是有問題的,例如:
static __thread pthread_key_t g_key;
本來pthread文檔中就明確表述key須要全局或者static存儲,而這裏加上__thread
後,其實每一個線程訪問的key都不是同一個key了哦。。
若是僅僅只是想用來在線程內部存儲一些簡單的int數據,而且不考慮完備的跨平臺支持,那麼建議直接使用__thread
或者__declspec(thread)
j就好了,很是方便易用
若是要考慮跨平臺操做的話,tbox的tls接口也是個不錯的選擇哦。。
我的主頁:TBOOX開源工程