1. 程式人生 > >b e g i n t h r e a d e x函式與C r e a t e T h r e a d函式

b e g i n t h r e a d e x函式與C r e a t e T h r e a d函式

                若要使多執行緒C和C + +程式能夠正確地執行,必須建立一個數據結構,並將它與使用C / C + +執行期庫函式的每個執行緒關聯起來。當你呼叫C / C + +執行期庫時,這些函式必須知道檢視呼叫執行緒的資料塊,這樣就不會對別的執行緒產生不良影響。那麼系統是否知道在建立新執行緒時分配該資料塊呢?回答是它不知道。系統根本不知道你得到的應用程式是用C / C + +編寫的,也不知道你呼叫函式的執行緒本身是不安全的。問題在於你必須正確地進行所有的操作。若要建立一個新執行緒,絕對不要呼叫作業系統的C r e a t e T h r e a d函式,必須呼叫C / C + +執行期庫函式_ b e g i n t h r e a d e x:_ b e g i n t h r e a d e x函式的引數列表與C r e a t e T h r e a d函式的引數列表是相同的,但是引數名和型別並不完全相同。這是因為M i c r o s o f t的C / C + +執行期庫的開發小組認為, C / C + +執行期函式不應該對Wi n d o w s資料型別有任何依賴。_ b e g i n t h r e a d e x函式也像C r e a t e T h r e a d那樣,返回新建立132計計第二部分程式設計的具體方法下載的執行緒的控制代碼。因此,如果呼叫原始碼中的C r e a t e T h r e a d,就很容易用對_ b e g i n t h r e a d e x的呼叫全域性取代所有這些呼叫。不過,由於資料型別並不完全相同,所以必須進行某種轉換,使編譯器執行得順利些。為了使操作更加容易,我在原始碼中建立了一個巨集c h B E G I N T H R E A D E X:注意,_ b e g i n t h r e a d e x函式只存在於C / C + +執行期庫的多執行緒版本中。如果連結到單執行緒執行期庫,就會得到一個連結程式報告的“未轉換的外部符號”錯誤訊息。當然,從設計上講,這個錯誤的原因是單執行緒庫在多執行緒應用程式中不能正確地執行。另外需要注意,當建立一個新專案時, Visual Studio預設選定單執行緒庫。這並不是最安全的預設設定,對於多執行緒應用程式來說,必須顯式轉換到多執行緒的C / C + +執行期庫。由於M i c r o s o f t為C / C + +執行期庫提供了原始碼,因此很容易準確地確定C r e a t e T h r e a d究竟無法執行哪些_ b e g i n t h r e a d e x能執行的操作。實際上,我搜索了Visual Studio 的光碟,發現_ b e g i n t h r e a d e x的原始碼在T h r e a d e x . c中。代換重新列印它的原始碼,這裡提供了它的虛擬碼版本,並且列出它的一些令人感興趣的要點:第6章執行緒的基礎知識計計133下載下面是關於_ b e g i n t h r e a d e x的一些要點:• 每個執行緒均獲得由C / C + +執行期庫的堆疊分配的自己的t i d d a t a記憶體結構。(t i d d a t a結構位於M t d l l . h檔案中的Visual C++原始碼中)。我在清單6 - 1中重建了它的結構。• 傳遞給_ b e g i n t h r e a d e x的執行緒函式的地址儲存在t i d d a t a記憶體塊中。傳遞給該函式的引數也儲存在該資料塊中。• _ b e g i n t h r e a d e x確實從內部呼叫C r e a t e T h r e a d,因為這是作業系統瞭解如何建立新執行緒的唯一方法。• 當呼叫C r e a t e t T h r e a d時,它被告知通過呼叫_ t h r e a d s t a r t e x而不是p f n S t a r t A d d r來啟動執行新執行緒。還有,傳遞給執行緒函式的引數是t i d d a t a結構而不是p v P a r a m的地址。• 如果一切順利,就會像C r e a t e T h r e a d那樣返回執行緒控制代碼。如果任何操作失敗了,便返回N U L L。清單6-1 C/C++執行期庫的執行緒區域性t i d d a t a結構134計計第二部分程式設計的具體方法下載既然為新執行緒指定了t i d d a t a結構,並且對該結構進行了初始化,那麼必須瞭解該結構與執行緒之間是如何關聯起來的。讓我們觀察一下_ t h r e a d s t a r t e x函式(它也位於C / C + +執行期庫的T h r e a d e x . c檔案中)。這裡是該函式的虛擬碼版本:第6章執行緒的基礎知識計計135下載下面是關於_ t h r e a d s t a r t e x的一些重點:• 新執行緒開始從B a s e t h r e a d S t a r t函式(在k e r n e l 3 2 . d l l檔案中)執行,然後轉移到_ t h r e a d s t a r t e x。• 到達該新執行緒的t i d d a t a塊的地址作為其唯一引數被傳遞給_ t h r e a d s t a r t e x。• T l s S e t Va l u e是個作業系統函式,負責將一個值與呼叫執行緒聯絡起來。這稱為執行緒本地儲存器(T L S),將在第2 1章介紹。_ t h r e a d s t a r t e x函式將t i d d a t a塊與執行緒聯絡起來。• 一個S E H幀被放置在需要的執行緒函數週圍。這個幀負責處理與執行期庫相關的許多事情—例如,執行期錯誤(比如放過了沒有抓住的C + +異常條件)和C / C + +執行期庫的s i g n a l函式。這是特別重要的。如果用C r e a t e T h r e a d函式來建立執行緒,然後呼叫C / C + +執行期庫的s i g n a l函式,那麼該函式就不能正確地執行。• 呼叫必要的執行緒函式,傳遞必要的引數。記住,函式和引數的地址由_ b e g i n t h r e a d e x儲存在t i d d a t a塊中。• 必要的執行緒函式返回值被認為是執行緒的退出程式碼。注意, _ t h r e a d s t a r t e x並不只是返回到B a s e T h r e a d S t a r t。如果它準備這樣做,那麼執行緒就終止執行,它的退出程式碼將被正確地設定,但是執行緒的t i d d a t a記憶體塊不會被撤消。這將導致應用程式中出現一個漏洞。若要防止這個漏洞,可以呼叫另一個C / C + +執行期庫函式_ e n d t h r e a d e x ,並傳遞退出程式碼。需要介紹的最後一個函式是_ e n d t h r e a d e x(位於C執行期庫的T h r e a d e x . c檔案中)。下面是該函式的虛擬碼版本:136計計第二部分程式設計的具體方法下載下面是關於_ e n d t h r e a d e x的一些要點:• C執行期庫的_ g e t p t d函式內部呼叫作業系統的T l s G e t Va l u e函式,該函式負責檢索呼叫執行緒的t i d d a t a記憶體塊的地址。• 然後該資料塊被釋放,而作業系統的E x i t T h r e a d函式被呼叫,以便真正撤消該執行緒。當然,退出程式碼要正確地設定和傳遞。本章前面說過,始終都應該設法避免使用E x i t T h r e a d函式。這一點完全正確,我並不想收回我已經說過的話。ExitThread 函式將撤消呼叫函式,並且不允許它從當前執行的函式返回。由於該函式不能返回,所以建立的任何C + +物件都不會被撤消。避免呼叫E x i t T h r e a d的另一個原因是,它會使得執行緒的t i d d a t a記憶體塊無法釋放,這樣,應用程式將會始終佔用記憶體(直到整個程序終止執行為止)。M i c r o s o f t的Visual C++開發小組認識到程式設計人員喜歡呼叫E x i t T h r e a d,因此他們實現了他們的願望,並且不會讓應用程式始終佔用記憶體。如果真的想要強制撤消執行緒,可以讓它呼叫_ e n d t h r e a d e x(而不是呼叫E x i t T h r e a d)以便釋放執行緒的t i d d a t a塊,然後退出。不過建議不要呼叫_ e n d t h r e a d e x函式。現在應該懂得為什麼C / C + +執行期庫的函式需要為它建立的每個執行緒設定單獨的資料塊,同時,也應該瞭解如何通過呼叫_ b e g i n t h r e a d e x來分配資料塊,再對它進行初始化,將該資料塊與你建立的執行緒聯絡起來。你還應該懂得_ e n d t h r e a d e x函式是如何線上程終止執行時釋放資料塊的。一旦資料塊被初始化並且與執行緒聯絡起來,執行緒呼叫的任何需要單執行緒例項資料的C / C + +執行期庫函式都能很容易地(通過T l s G e t Va l u e)檢索呼叫執行緒的資料塊地址,並對執行緒的資料進行操作。這對於函式來說很好,但是你可能想知道它對e r r n o之類的全域性變數效果如何。E r r n o定義在標準的C標頭檔案中,類似下面的形式:如果建立一個多執行緒應用程式,必須在編譯器的命令列上設定/ M T(指多執行緒應用程式)或/ M D(指多執行緒D L L)開關。這將使編譯器能夠定義_ M T識別符號。然後,每當引用e r r n o時,實際上是呼叫內部的C / C + +執行期庫函式_ e r r n o。該函式返回呼叫執行緒的相關資料塊中的e r r n o資料成員的地址。你將會發現, e r r n o巨集被定義為獲取該地址的內容的巨集。這個定義是必要的,因為可以編寫類似下面形式的程式碼:如果內部函式_ e r r n o只返回e r r n o的值,那麼上面的程式碼將不進行編譯。第6章執行緒的基礎知識計計137下載多執行緒版本的C / C + +執行期庫還給某些函式設定了同步的基本要素。例如,如果兩個執行緒同時呼叫m a l l o c,那麼記憶體堆疊就可能遭到破壞。多執行緒版本的C / C + +執行期庫能夠防止兩個執行緒同時從堆疊中分配記憶體。為此,它要讓第二個執行緒等待,直到第一個執行緒從m a l l o c返回。然後第二個執行緒才被允許進入(關於執行緒同步的問題將在第8、9章和1 0章詳細介紹)。顯然,所有這些附加操作都會影響多執行緒版本的C / C + +執行期庫的效能。這就是為什麼M i c r o s o f t公司除了多執行緒版本外,還提供單執行緒版本的靜態連結的C / C + +執行期庫的原因。C / C + +執行期庫的動態連線版本編寫成為一種通用版本。這樣它就可以被使用C / C + +執行期庫函式的所有正在執行的應用程式和D L L共享。由於這個原因,執行期庫只存在於多執行緒版本中。由於D L L中提供了C / C + +執行期庫,因此應用程式(. e x e檔案)和D L L不需要包含C / C + +執行期庫函式的程式碼,結果它們的規模就比較小。另外,如果M i c r o s o f t排除了C / C + +執行期庫D L L中的錯誤,應用程式中的錯誤也會自動得到解決。