線程局部存儲作用
❶ 線程特有數據(Thread Specific Data)
在單線程程序中,我們經常要使用 全局變數 來實現多個函數間共享數據。在多線程環境下,由於數據空間是共享的,因此全局變數也為所有線程所共有。但有時在應用程序設計中有必要提供 線程私有 的全局變數,僅在某個線程中有效,但可以跨多個函數訪問,這樣每個線程訪問它自己獨立的數據空間,而不用擔心和其它線程的同步訪問。
這樣在一個線程內部的各個函數都能訪問、但其它線程不能訪問的變數,我們就需要使用 線程局部靜態變數 (Static memory local to a thread) 同時也可稱之為 線程特有數據 (Thread-Specific Data 或 TSD),或者 線程局部存儲 (Thread-Local Storage 或 TLS)。
POSIX 線程庫提供了如下 API 來管理線程特有數據(TSD):
第一參數 key 指向 pthread_key_t 的對象的指針。請 注意 這里 pthread_key_t 的對象佔用的空間是用戶事先分配好的, pthread_key_create 不會動態生成 pthread_key_t 對象。
第二參數 desctructor ,如果這個參數不為空,那麼當每個線程結束時,系統將調用這個函數來釋放綁定在這個鍵上的內存塊。
有時我們在線程里初始化時,需要避免重復初始化。我們希望一個線程里只調用 pthread_key_create 一次,這時就要使用 pthread_once 與它配合。
第一個參數 once_control 指向一個 pthread_once_t 對象,這個對象必須是常量 PTHREAD_ONCE_INIT ,否則 pthread_once 函數會出現不可預料的結果。
第二個參數 init_routine ,是調用的初始化函數,不能有參數,不能有返回值。
如果成功則返回0,失敗返回非0值。
創建完鍵後,必須將其與線程數據關聯起來。關聯後也可以獲得某一鍵對應的線程數據。關聯鍵和數據使用的函數為:
第一參數 key 指向鍵。
第二參數 value 是欲關聯的數據。
函數成功則返回0,失敗返回非0值。
注意: 用 pthread_setspecific 為一個鍵指定新的線程數據時,並不會主動調用析構函數釋放之前的內存,所以調用線程必須自己釋放原有的線程數據以回收內存。
獲取與某一個鍵關聯的數據使用函數的函數為:
參數 key 指向鍵。
如果有與此鍵對應的數據,則函數返回該數據,否則返回NULL。
刪除一個鍵使用的函數為:
參數 key 為要刪除的鍵。
成功則返回0,失敗返回非0值。
注意: 該函數將鍵設置為可用,以供下一次調用 pthread_key_create() 使用。它並不檢查當前是否有線程正在使用該鍵對應的線程數據,所以它並不會觸發函數 pthread_key_create 中定義的 destructor 函數,也就不會釋放該鍵關聯的線程數據所佔用的內存資源,而且在將 key 設置為可用後,在線程退出時也不會再調用析構函數。所以在將 key 設置為可用之前,必須要確定:
在 linux 中每個進程有一個全局的數組 __pthread_keys ,數組中存放著 稱為 key 的結構體,定義類似如下:
在 key 結構中 seq 為一個序列號,用來作為使用標志指示這個結構在數組中是否正在使用,初始化時被設為0,即表示 不在使用 。 destructor 用來存放一個析構函數指針。
pthread_create_key 會從數組中找到一個還未使用的 key 元素,將其序列號 seq 加1,並記錄析構函數地址,並將 key 在數組 __pthread_keys 中的 下標 作為返回值返回。那麼如何判斷一個 key 正在使用呢?
如果 key 的序列號 seq 為偶數則表示未分配,分配時將 seq 加1變成奇數,即表示正在使用。這個操作過程採用原子 CAS 來完成,以保證線程安全。在 pthread_key_delete() 時也將序列號 seq 加1,表示可以再被使用,通過序列號機制來保證回收的 key 不會被復用(復用 key 可能會導致線程在退出時可能會調用錯誤的析構函數)。但是一直加1會導致序列號回繞,還是會復用 key ,所以調用 pthread_create_key 獲取可用的 key 時會檢查是否有回繞風險,如果有則創建失敗。
除了進程范圍內的 key 結構數組外,系統還在進程中維護關於每個線程的控制塊 TCB(用於管理寄存器,線程棧等),裡面有一個 pthread_key_data 類型的數組。這個數組中的元素數量和進程中的 key 數組數量相等。 pthread_key_data 的定義類似如下:
根據 pthread_key_create() 返回的可用的 key 在 __pthread_keys 數組中的下標, pthread_setspecific() 在 pthread_key_data 的數組 中定位相同下標的一個元素 pthread_key_data ,並設置其序號 seq 設置為對應的 key 的序列號,數據指針 data 指向設置線程特有數據(TSD)的值。
pthread_getspecific() 用於將 pthread_setspecific() 設置的 data 取出。
線程退出時, pthread_key_data 中的序號 seq 用於判斷該 key 是否仍在使用中(即與在 __pthread_keys 中的同一個下標對應的 key 的序列號 seq 是否相同),若是則將 pthread_key_data 中 data(即 線程特有數據 TSD)作為參數調用析構函數。
由於系統在每個進程中 pthread_key_t 類型的數量是有限的,所有在進程中並不能獲取無限個 pthread_key_t 類型。Linux 中可以通過 PTHREAD_KEY_MAX(定義於 limits.h 文件中)或者系統調用 sysconf(_SC_THREAD_KEYS_MAX) 來確定當前系統最多支持多少個 key 。 Linux 中默認是 1024 個 key,這對大多數程序來書已經夠了。如果一個線程中有多個線程局部存儲變數(TLS),通常可以將這些變數封裝到一個數據結構中,然後使用封裝後的數據結構和一個線程局部變數相關聯,這樣就能減少對鍵值的使用。
https://blog.csdn.net/hustraiet/article/details/9857919
https://blog.csdn.net/hustraiet/article/details/9857919
https://blog.csdn.net/caigen1988/article/details/7901248
http://www.bitools.com/?p=2443
https://spockwangs.github.io/blog/2017/12/01/thread-local-storage/
https://www.jianshu.com/p/71c2f80d7bd1
https://blog.csdn.net/cywosp/article/details/26469435
http://www.embeddedlinux.org.cn/emblinuxappdev/117.htm
❷ 線程局部存儲的簡介
在一個線程修改的內存內容,對所有線程都生效。這是一個優點也是一個缺點。說它是優點,線程的數據交換變得非常快捷。說它是缺點,一個線程死掉了,其它線程也性命不保; 多個線程訪問共享數據,需要昂貴的同步開銷,也容易造成同步相關的BUG。
如果需要在一個線程內部的各個函數調用都能訪問、但其它線程不能訪問的變數(被稱為static memory local to a thread 線程局部靜態變數),就需要新的機制來實現。這就是TLS。
線程局部存儲在不同的平台有不同的實現,可移植性不太好。幸好要實現線程局部存儲並不難,最簡單的辦法就是建立一個全局表,通過當前線程ID去查詢相應的數據,因為各個線程的ID不同,查到的數據自然也不同了。但Windows系統採用了每個線程建線程專享的索引表,表的條目為線程局部存儲的地址。在線程執行的任何代碼處,都可以查詢本線程的這個索引表獲得要訪問的線程局部存儲的地址。
大多數平台都提供了線程局部存儲的方法,無需要我們自己去實現:
❸ threadlocal使用場景和原理是什麼
ThreadLocal主要應用於那些每個線程需要擁有獨立的、不會被其他線程共享的數據存儲場景。當一個變數需要在多個方法中使用,但又不需要跨線程共享時,ThreadLocal就派上用場。它的核心原理在於為每個線程創建一個獨立的變數副本,從而避免了多線程環境下的同步和並發問題。
相反,線程同步的主要目標是解決多線程中對共享變數的並發訪問。它確保每個線程都能准確獲取到變數的最新值,防止數據競爭。比如在多個線程同時寫入同一個變數時,為了防止數據不一致,通常需要使用鎖等同步機制來控制訪問。
ThreadLocal的設計思想是為每個線程提供一個線程局部存儲空間,這樣就無需在整個線程中進行同步,簡化了並發控制,提高了效率。在Spring框架中,ThreadLocal被廣泛用於需要線程隔離的場景,如保存用戶的會話信息等,以避免跨線程操作帶來的復雜性。