日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Win32多线程编程(5) — 线程局部存储

發布時間:2024/4/11 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Win32多线程编程(5) — 线程局部存储 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

預留內存攜帶附加信息的設計

有時候,將數據與一個對象的實例關聯起來是很有幫助的。這種設計要求預留一定的內存,一倍特定附加數據的存儲。

通過調用SetWindowWordSetWindowLong函數將數據與一個指定的窗口關聯起來,數據保存在窗口附加內存塊中。窗口內存塊即是一種窗口對象(HWND)的附加數據(window extra bytes),參考WNDCLASS.cbWndExtra字段(Specifies the number of extra bytes to allocate following the window instance.)。

這種預留附加的設計,在MFC中處處可見。對于下拉選擇列表(CComboBox)、下拉列表框、列表視圖和樹控件,我們不光希望其能顯示條目內容(item text),還希望每個條目能夠攜帶附加信息,即存儲額外的關聯數據(item data),以備不時之需。這四個控件都提供了SetItemData/GetItemData接口,供用戶儲存關聯數據。存儲的數據為DWORD值類型,可以是簡單的數值,也可以存儲指針。

?

線程消息隊列和_ptiddata

我們在編寫第一個SDK窗口程序時,就接觸到了消息這一重要概念。實際上,消息隊列是一種線程私有數據,每一個Windows程序的UI(CUI/GUI)線程都維持了一個消息隊列。GetMessageTranslateMessageDispatchMessage等對消息的操作都是與調用線程的消息隊列息息相關。PostThreadMessage是線程消息投遞函數,它向一個指定ID(idThread)的線程發送一條消息,然后不等處理立即返回。這個API在多線程架構程序中非常有用。PostQuitMessage是結束線程運行,相當于nExitCode作為WM_QUIT消息參數調用PostThreadMessage。調用線程收到該消息后即ExitThread,故該函數一般用來響應WM_DESTROY消息。

盡管秉持封裝的原則,我們極力強調避免使用全局變量,但全局變量對于進程級和線程級的系統統籌管理卻是非常有用。除了消息隊列這種系統內置的線程私有數據外,Windows提供了線程局部存儲系統(TLS,Thread Local Storage),為用戶提供了存儲與線程關聯數據的接口。前面提到的_beginthreadex中分配的_ptiddatapointer to per-thread data),即使用了TLS。_ptiddata為Windows平臺的多線程程序中,strtokstrerrorerrno等依賴全局變量或靜態變量的CRT函數的實現提供了有效的解決方案。

?

Win32線程局部存儲系統

用于管理?TLS?的數據結構是很簡單的,Windows僅為系統中的每一個進程維護一個位數組,再為該進程中的每一個線程申請一個同樣長度的數組空間,如下圖所示。

????在Windbg中,可以窺探TEB中的TLS數據結構。

lkd> dt _teb

nt!_TEB

???+0x02c ThreadLocalStoragePointer : Ptr32 Void

???+0xe10 TlsSlots?????????: [64] Ptr32 Void

???+0xf10 TlsLinks?????????: _LIST_ENTRY

???+0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void

?

typedef struct?_TEB?// 66 elements, 0xFB8 bytes (sizeof)

{

????// ……

????/*0x02C*/?????VOID*????????ThreadLocalStoragePointer;

????// ……

????/*0xE10*/?????VOID*????????TlsSlots[64];

????/*0xF10*/?????struct _LIST_ENTRY?TlsLinks;?// 2 elements, 0x8 bytes (sizeof)

????// ……

????/*0xF94*/?????VOID**???????TlsExpansionSlots;

????// ……

}TEB, *PTEB;

當一個線程被創建時,Windows就會在進程地址空間中為該線程分配一個長度為TLS_MINIMUM_AVAILABLE的數組,數組成員的值都被初始化為?0。在內部,系統將此數組與該線程關聯起來,保證只能在該線程中訪問此數組中的數據。如上圖所示,每個線程都有它自己的數組,數組成員可以存儲任何數據。

運行在系統中的每一個進程都有上圖所示的一個位數組。位數組的成員是一個標志,每個標志的值被設為FREE或INUSE,指示了此標志對應的數組索引是否在使用中。Windows?保證至少有TLS_MINIMUM_AVAILABLE(定義在WinNT.h文件中)個標志位可用。

動態使用TLS典型步驟如下。

(1)主線程調用TlsAlloc函數為線程局部存儲分配索引,函數原型如下。

DWORD?TlsAlloc(VOID);

TlsAlloc為我們預訂了一個索引。如果TlsAlloc返回的索引為3,那等于說索引3已經被我們預訂了,無論是進程中當前正在運行的線程,還是今后可能會創建的線程,都不能再使用索引3。

(2)每個線程調用TlsSetValueTlsGetValue設置或讀取線程數組中的值,這兩個函數的原型如下。

BOOL?TlsSetValue(

???????????????DWORD?dwTlsIndex,??// TLS index

???????????????LPVOID?lpTlsValue??// value to store

);

?

LPVOID?TlsGetValue(

?????????????????DWORD?dwTlsIndex???// TLS index

);

(3)主線程調用TlsFree釋放局部存儲索引。函數的惟一參數是TlsAlloc返回的索引。

BOOL?TlsFree(

????????????DWORD?dwTlsIndex???// TLS index

????????????);

?

MFC中的線程局部存儲

如果你需要大量的數據貫穿一個線程,普通的TLS索引一個值就會變得不實用,Windows的TLS只允許用戶保存一個32位的指針。如果需要用戶保存任意類型的數據(包含整個類)。這個任意大小的數據所占的內存通常是在進程的堆中分配,所以當用戶釋放全局索引時,系統必須將每個線程內此數據占用的內存釋放掉,這就要求系統把為各線程分配的內存都記錄下來。較好的方法是將各個私有數據的首地址用一個鏈表連在一起,釋放全局索引時只要遍歷此鏈表,就可以逐個釋放線程私有數據占用的空間了。

例如,有下面一個存放線程私有數據的數據結構。

struct?CThreadData

{

????CThreadData*?pNext;?//?指向下一個線程的CThreadData結構的指針

????LPVOID?pData;???????//?指向真正的線程私有數據的指針

};

指針?pData指向為線程分配的內存的首地址,指針pNext將各線程的數據連在了一起。這實際上是一種二級指針的分槽存儲。MFC的線程局部存儲類CThreadLocal即實現二級指針的分槽存儲。

MFC框架的狀態信息也是理解的難點,包括模塊狀態AFX_MODULE_STATE線程狀態_AFX_THREAD_STATE和模塊線程狀態AFX_MODULE_THREAD_STATE。這些線程級別的全局狀態維持即使用了線程局部存儲(TLS)。參考李久進著作的《MFC深入淺出》第九章《MFC的狀態》。

由于MFC廣泛地應用了線程局部存儲,故在MFC下,使用線程必須格外小心。許多MFC對象僅在創建它們的線程內運作。一般地,具有句柄映射的任何對象都不能從其他線程訪問該對象。例如,模塊線程狀態AFX_MODULE_THREAD_STATE中的CHandleMap*?m_pmapHWND映射記錄了MFC線程中創建的CWnd對象實例與內核窗口句柄(HWND)之間的映射消息。內核窗口句柄是可以進程訪問級別,因此可跨線程訪問。但是試圖傳遞CWnd對象實例以期跨線程操作,往往失敗。因為另一個引用線程并未像創建線程那樣維系一個映射,所以當需要CWndàHWND以執行API操作時,往往找不到其所指窗口。

針對以上問題,通常優先傳送句柄,避免在線程之間傳送MFC對象。在引用線程中將其轉換為臨時MFC對象。例如,假設線程?A創建一個CWnd對象。線程A并不將對象傳送給線程B,而將該對象的m_hWnd成員傳送給線程B。于是,線程B可以調用CWnd::FromHandle,以創建一個臨時的CWnd對象。如果線程B需要更持久的連接,就可以使用Attach方法,在窗口及其CWnd對象之間建立持久的關聯。

另外的一個常見問題是MFC對象訪存的線程安全性問題。MFC對象不會自動在不同的線程之間做出判斷。所以,如果兩個線程試圖同時訪問同一個CString類的對象,結果可能受到嚴重破壞。只有防止來自有沖突的MFC對象的線程。通常,這將需要使用前面提到的同步機制,以保證多線程數據交換的一致性。

?

參考:

《為什么要用TLS》

《WIN32下線程和窗口的數據綁定》

總結

以上是生活随笔為你收集整理的Win32多线程编程(5) — 线程局部存储的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。