Windows核心编程 第六章 线程基础知识 (上)
第6章?線程的基礎知識
? ? 理解線程是非常關鍵的,因為每個進程至少需要一個線程。本章將更加詳細地介紹線程的知識。尤其是要講述進程與線程之間存在多大的差別,它們各自具有什么作用。還要介紹系統如何使用線程內核對象來管理線程。與進程內核對象一樣,線程內核對象也擁有屬性,我們將要觀察許多用于查詢和修改這些屬性的函數。此外還要介紹可以在進程中創建和生成更多的線程時所用的函數。
? ? 第4章介紹了進程是由兩個部分構成的,一個是進程內核對象,另一個是地址空間。同樣,線程也是由兩個部分組成的:
????? 一個是線程的內核對象,操作系統用它來對線程實施管理。內核對象也是系統用來存放線程統計信息的地方。
? 另一個是線程堆棧,它用于維護線程在執行代碼時需要的所有函數參數和局部變量(第1 6章將進一步介紹系統如何管理線程堆棧) 。
? ? 第4章中講過,進程是不活潑的。進程從來不執行任何東西,它只是線程的容器。線程總是在某個進程環境中創建的,而且它的整個壽命期都在該進程中。這意味著線程在它的進程地址空間中執行代碼,并且在進程的地址空間中對數據進行操作。因此,如果在單進程環境中,你有兩個或多個線程正在運行,那么這兩個線程將共享單個地址空間。這些線程能夠執行相同的代碼,對相同的數據進行操作。這些線程還能共享內核對象句柄,因為句柄表依賴于每個進程而不是每個線程存在。
???如你所見,進程使用的系統資源比線程多得多,原因是它需要更多的地址空間。為進程創建一個虛擬地址空間需要許多系統資源。系統中要保留大量的記錄,這要占用大量的內存。另外,由于. e x e和. d l l文件要加載到一個地址空間,因此也需要文件資源。而線程使用的系統資源要少得多。實際上,線程只有一個內核對象和一個堆棧,保留的記錄很少,因此需要很少的內存。
? ? 由于線程需要的開銷比進程少,因此始終都應該設法用增加線程來解決編程問題,而要避免創建新的進程。但是,這個建議并不是一成不變的。許多程序設計用多個進程來實現會更好些。應該懂得權衡利弊,經驗會指導你的編程實踐。
? ? 在詳細介紹線程之前,首先花一點時間講一講如何正確地在應用程序結構中使用線程。
6.1 何時創建線程
????線程用于描述進程中的運行路徑。每當進程被初始化時,系統就要創建一個主線程。該線程與C / C + +運行期庫的啟動代碼一道開始運行,啟動代碼則調用進入點函數( m a i n、w m a i n、Wi n M a i n或w Wi n M a i n) ,并且繼續運行直到進入點函數返回并且 C / C + +運行期庫的啟動代碼調用E x i t P r o c e s s為止。對于許多應用程序來說,這個主線程是應用程序需要的唯一線程。不過,
進程能夠創建更多的線程來幫助執行它們的操作。
每個計算機都擁有一個功能非常強大的資源,即 C P U。讓C P U閑置起來是絕對沒有道理的(如果忽略節省電能問題的話) 。為了使C P U處于繁忙狀態之中,可以讓它執行各種不同的工作。
下面是一些例子:
????? 可以打開Microsoft Windows 2000配備的內容索引服務程序。它能夠創建一個低優先級的線程,以便定期打開你的磁盤驅動器上的文件內容并給內容做索引。若要找到一個文件,可以打開Search Result(搜索結果)窗口(方法是單擊 S t a r t按鈕,從S e a r c h菜單中選定For Files Or Folders) ,再將你的搜索條件輸入Containing Te x t域。這時就可以搜索到索引,相關的文件就會立即顯示出來。內容索引服務程序大大改進了性能,因為每次搜索不必打開、掃描和關閉磁盤驅動器上的每個文件。
????? 可以使用Windows 2000配備的磁盤碎片整理軟件。通常情況下,這種類型的實用程序擁有許多管理選項,一般用戶可能不懂,比如該實用程序應該相隔多長時間運行一次,何時運行。使用低優先級線程,可以在后臺運行該實用程序,并且在系統空閑時對驅動器進行碎片整理。
????? 可以很容易地設想將來版本的編譯器,每當暫停鍵入時,它就可以自動編譯你的源代碼文件。輸出窗口可以向你(幾乎)實時顯示警告和出錯信息。當鍵入變量和函數名時出現錯誤時,就能立即發現。在某種程度上講, Microsoft Visual Studio已經實現了這個功能,使用Wo r k s p a c e的C l a s s Vi e w窗格,就能夠看到這些信息。
????? 電子表格應用程序能夠在后臺執行各種計算。
????? 字處理程序能夠執行重新分頁、拼寫和語法檢查及在后臺進行打印。
????? 文件可以在后臺拷貝到其他介質中。
? We b瀏覽器在后臺與它們的服務器進行通信。因此,在來自當前 We b站點的結果輸入之前,用戶可以縮放瀏覽器的窗口或者轉到另一個We b站點。
這些例子中,有一個重要問題應該注意,那就是多線程能夠簡化應用程序的用戶界面。如果每當停止鍵入時,編譯器建立了你的應用程序,那么就沒有必要提供 B u i l d菜單選項。文字處理應用程序不需要Check Spelling(拼寫檢查)和Check Grammar(語法檢查)菜單選項。
在We b瀏覽器的例子中,注意,將不同的線程用于 I / O(網絡、文件或其他) ,應用程序的用戶界面就能夠始終保持工作狀態。比如有一個應用程序負責給數據庫記錄進行排序、打印文檔或拷貝文件。如果將獨立的線程用于處理這個與 I / O相關的任務,用戶就可以在進程中繼續使用應用程序界面來取消操作。
設計一個擁有多線程的應用程序,就會擴大該應用程序的功能。我們在下一章中可以看到,每個線程被分配了一個C P U。因此,如果你的計算機擁有兩個C P U,你的應用程序中有兩個線程,那么兩個C P U都將處于繁忙狀態。實際上,你是讓兩個任務在執行一個任務的時間內完成操作。
每個進程至少擁有一個線程。因此,如果你在應用程序中不執行任何特殊的操作,在多進程操作系統上運行,就能夠得到許多好處。例如,可以建立一個應用程序,并同時使用文字處理程序(我常常這樣做) 。如果計算機擁有兩個C P U,那么該應用程序就可以在一個處理器上執行,而另一個處理器則負責處理文檔。另外,如果編譯器出現一個錯誤,導致它的線程進入一個無限循環,仍然可以使用其他的進程(1 6位Wi n d o w s和M S - D O S應用程序則不行) 。
?
6.2 何時不能創建線程
至今為止,一直在討論多線程應用程序的優點。雖然多線程應用程序的優點很多,但是它也存在某些不足之處。有些開發人員認為,解決問題的方法是將它分割成多個線程。這種想法是完全錯誤的。
線程確實是非常有用的,但是,當使用線程時,在解決原有的問題時可能產生新的問題。
????例如,你開發了一個文字處理應用程序,并且想要讓打印函數作為它自己的線程來運行。這聽起來是個很好的主意,因為用戶可以在打印文檔時立即回頭著手編輯文檔。但是,這意味著文檔中的數據可能在文檔打印時變更。也許最好是不要讓打印操作在它自己的線程中發生,不過這種“方案”看起來有點兒極端。如果你讓用戶編輯另一個文檔,但是鎖定正在打印的文檔,使得打印結束前該文檔不能修改,那將會怎樣呢?這里還有第三種思路,將文檔拷貝到一個臨
時文件,然后打印該臨時文件的內容,并讓用戶修改原始文檔。當包含該文檔的臨時文件結束打印時,刪除臨時文件。
如你所見,線程能夠解決某些問題,但是卻又會產生新的問題。在開發應用程序的用戶界面時,很可能出現對線程的另一種誤用。幾乎在所有的應用程序中,所有用戶界面的組件(窗口)應該共享同一個線程。單個線程應該創建窗口的所有子窗口。有時在不同的線程上創建不同的窗口是有用的,不過這種情況確實非常少見。
通常情況下,一個應用程序擁有一個用戶界面線程,用于創建所有窗口,并且有一個G e t M e s s a g e循環。進程中的所有其他線程都是工作線程,它們與計算機或 I / O相關聯,但是這些線程從不創建窗口。另外,一個用戶界面線程通常擁有比工作線程更高的優先級,因此用戶界面負責向用戶作出響應。
雖然單個進程擁有多個用戶界面線程的情況并不多見, 但是這種情況有著某種有效的用途。Windows Explorer為每個文件夾窗口創建了一個獨立的線程。它使你能夠將文件從一個文件夾拷貝到另一個文件夾,并且仍然可以查看你的系統上的其他文件夾。另外,如果 E x p l o r e r中存在一個錯誤,那么負責處理文件夾的線程可能崩潰,但是仍然能夠對其他文件夾進行操作,至少在執行的操作導致其他文件夾也崩潰之前,仍然可以對它們進行操作(關于線程和用戶界面
的詳細說明,參見第2 6和2 7章) 。
上述內容的實質是應該慎重地使用多線程。不要想用就用。僅僅使用賦予進程的主線程,就能夠編寫出許多非常有用的和功能強大的應用程序。
6.3 編寫第一個線程函數
? ?每個線程必須擁有一個進入點函數,線程從這個進入點開始運行。前面已經介紹了主線程的進入點函數:即m a i n、w m a i n、Wi n M a i n或w Wi n M a i n。如果想要在你的進程中創建一個輔助線程,它必定也是個進入點函數,類似下面的樣子:
?
? ?你的線程函數可以執行你想要它做的任何任務。最終,線程函數到達它的結尾處并且返回。這時,線程終止運行,該堆棧的內存被釋放,同時,線程的內核對象的使用計數被遞減。如果使用計數降為0,線程的內核對象就被撤消。與進程內核對象的情況相同,線程內核對象的壽命至少可以達到它們相關聯的線程那樣長,不過,該對象的壽命可以遠遠超過線程本身的壽命。
下面對線程函數的幾個問題作一說明:
????? 主線程的進入點函數的名字必須是m a i n、w m a i n、Wi n M a i n或w Wi n M a i n,與這些函數不同的是,線程函數可以使用任何名字。實際上,如果在應用程序中擁有多個線程函數,必須為它們賦予不同的名字,否則編譯器/鏈接程序會認為你為單個函數創建了多個實現函數。
? 由于給你的主線程的進入點函數傳遞了字符串參數,因此可以使用 A N S I / U n i c o d e版本的進入點函數:m a i n / w m a i n和Wi n M a i n / w Wi n M a i n。可以給線程函數傳遞單個參數,參數的含義由你而不是由操作系統來定義。因此,不必擔心 A N S I / U n i c o d e問題。
? 線程函數必須返回一個值,它將成為該線程的退出代碼。這與 C / C + +運行期庫關于讓主線程的退出代碼作為進程的退出代碼的原則是相似的。
? 線程函數(實際上是你的所有函數)應該盡可能使用函數參數和局部變量。當使用靜態變量和全局變量時,多個線程可以同時訪問這些變量,這可能破壞變量的內容。然而,參數和局部變量是在線程堆棧中創建的,因此它們不太可能被另一個線程破壞。
6.4 CreateThread函數
? ? 前面已經講述了調用C r e a t e P r o c e s s函數時如何創建進程的主線程。如果想要創建一個或多個輔助函數,只需要讓一個已經在運行的線程來調用C r e a t e T h r e a d:
?
? ? 當C r e a t e T h r e a d被調用時,系統創建一個線程內核對象。該線程內核對象不是線程本身,而是操作系統用來管理線程的較小的數據結構。可以將線程內核對象視為由關于線程的統計信息組成的一個小型數據結構。這與進程和進程內核對象之間的關系是相同的。
? ? 系統從進程的地址空間中分配內存,供線程的堆棧使用。新線程運行的進程環境與創建線程的環境相同。因此,新線程可以訪問進程的內核對象的所有句柄、進程中的所有內存和在這個相同的進程中的所有其他線程的堆棧。這使得單個進程中的多個線程確實能夠非常容易地互相通信。
? ? 注意 C r e a t e T h r e a d函數是用來創建線程的 Wi n d o w s函數。不過,如果你正在編寫C / C + +代碼,決不應該調用 C r e a t e T h r e a d。相反,應該使用Visual C++運行期庫函數_ b e g i n t h r e a d e x。如果不使用M i c r o s o f t的Visual C++編譯器,你的編譯器供應商有它自己的C r e a t e T h r e d替代函數。不管這個替代函數是什么,你都必須使用。本章后面將要介紹_ b e g i n t h r e a d e x能夠做什么,它的重要性何在。
這就是Create Thread函數的概述,下面各節將要具體介紹C r e a t e T h r e a d的每個參數。
6.4.1 psa
p s a參數是指向S E C U R I T Y _ AT T R I B U T E S結構的指針。如果想要該線程內核對象的默認安全屬性,可以(并且通常能夠)傳遞 N U L L。如果希望所有的子進程能夠繼承該線程對象的句柄,必須設定一個S E C U R I T Y _ AT T R I B U T E S結構,它的b I n h e r i t H a n d l e成員被初始化為T R U E。詳細信息參見第3章。
6.4.2 cbStack
? ? c b S t a c k參數用于設定線程可以將多少地址空間用于它自己的堆棧。每個線程擁有它自己的堆棧。當C r e a t e P r o c e s s啟動一個進程時,它就在內部調用 C r e a t e T h r e a d來對進程的主線程進行初始化。對于c b S t a c k參數來說,C r e a t e P r o c e s s使用存放在可執行文件中的一個值。可以使用鏈接程序的/ S TA C K開關來控制這個值:
/STACK:[reserve][.commit]
r e s e r v e參數用于設定系統應該為線程堆棧保留的地址空間量。默認值是 1 MB。C o m m i t參數用于設定開始時應該承諾用于堆棧保留區的物理存儲器的容量。默認值是 1頁。當線程中的代碼執行時,可能需要多個頁面的存儲器。當線程溢出它的堆棧時,就生成一個異常條件(關于線程堆棧和堆棧溢出的異常條件的詳細說明,參見第 1 6章,關于一般異常條件的處理的詳細說明,參見第2 3章) 。系統抓取該異常條件,并且將另一頁(或者你為 c o m m i t參數設定的任何值)用于保留空間,這使得線程的堆棧能夠根據需要動態地擴大。
當調用C r e a t e T h r e a d時,如果傳遞的值不是0,就能使該函數將所有的存儲器保留并分配給線程的堆棧。由于所有的存儲器預先作了分配,因此可以確保線程擁有指定容量的可用堆棧存儲器。保留空間的容量既可以是/ S TA C K鏈接程序設定的容量,也可以是C b S t a c k的值,誰大就用誰。分配的存儲器容量應該與傳遞的 c b S t a c k值相一致。如果將 0傳遞給 C b S t a c k參數,C r e a t e T h r e a d就保留一個區域,并且將鏈接程序嵌入 . e x e文件的/ S TA C K鏈接程序開關信息指明的存儲器容量分配給線程堆棧。
6.4.3 pfnStartAddr和p v P a r a m
? ? p f n S t a r t A d d r參數用于指明想要新線程執行的線程函數的地址。線程函數的 p v P a r a m參數與原先傳遞給C r e a t e T h r e a d的p v P a r a m參數是相同的。C r e a t e T h r e a d使用該參數不做別的事情,只是在線程啟動執行時將該參數傳遞給線程函數。該參數提供了一個將初始化值傳遞給線程函數的手段。該初始化數據既可以是數字值,也可以是指向包含其他信息的一個數據結構的指針。
創建多個線程,使這些線程擁有與起始點相同的函數地址,這是完全合乎邏輯的并且是非常有用的。例如,可以實現一個We b服務器,以便創建一個新線程來處理每個客戶機的請求。每個線程都知道它正在處理哪個客戶機的請求,因為當創建線程時,你傳遞了一個不同的 p z P a r a m值。
記住,Wi n d o w s是個搶占式多線程系統,這意味著新線程和調用 C r e a t e T h r e a d的線程可以同時執行。由于線程可以同時運行,就會出現一些問題。請看下面的代碼:
?
? ? 在上面這個代碼中,F i r s t T h r e a d可以在S e c o n d T h r e a d將5分配給F i r s t T h r e a d的x之前結束它的操作。如果出現這種情況,S e c o n d T h r e a d將不知道F i r s t T h r e a d已經不再存在,并且仍然試圖修改現在已經是個無效地址的內容。這會導致S e c o n d T h r e a d產生一次訪問違規,因為F i r s t T h r e a d的堆棧已經在F i r s t T h r e a d終止運行時被撤消。解決這個問題的方法之一是將 x聲明為一個靜態變量,這樣,編譯器就為應用程序的數據部分中的x創建一個存儲區,而不是在堆棧上創建存儲區。
但是這使得函數成為不可重新進入的函數。換句話說,無法創建兩個執行相同函數的線程,因為兩個線程將共享該靜態變量。解決這個問題(和它的更復雜的變形)的另一種方法是使用正確的線程同步技術(第8、9章和1 0章介紹) 。
?
6.4.4 fdwCreate
????f d w C r e a t e參數可以設定用于控制創建線程的其他標志。它可以是兩個值中的一個。如果該值是0,那么線程創建后可以立即進行調度。如果該值是 C R E AT E _ S U S P E N D E D,系統可以完整地創建線程并對它進行初始化,但是要暫停該線程的運行,這樣它就無法進行調度。
C R E AT E _ S U S P E N D E D標志使得應用程序能夠在它有機會執行任何代碼之前修改線程的某些屬性。由于這種必要性很少,因此該標志并不常用。第 5章介紹的J o b L a b應用程序說明了該標志的正確方法。
6.4.5 pdwThreadID
? ? C r e a t e T h r e a d的最后一個參數是p d w T h r e a d I D,它必須是D W O R D的一個有效地址,C r e a t e T h r e a d使用這個地址來存放系統分配給新線程的 I D (進程和線程的I D已經在第4章中作了介紹)。
注意 在Windows 2000(和Windows NT 4)下,可以(并且通常是這樣做的)為該參數傳遞N U L L。它告訴函數,你對線程的 I D不感興趣,但是線程已經創建了。在Windows 95和Windows 98下,為該參數傳遞N U L L會導致函數運行失敗,因為函數試圖將I D寫入地址N U L L(這是不合法的) 。因此線程不能創建。
當然,操作系統之間的不一致現象會給編程人員帶來一些問題。例如,在Wi n d o w s2 0 0 0下(即使為p d w T h r e a d I D參數傳遞了N U L L,它也創建了該線程)編寫和測試了一個應用程序,當后來在Windows 98上運行該應用程序時,C r e a t e T h r e a d將不創建新的線程。必須始終在你聲稱支持的所有操作系統(和所有版本)上充分測試應用程序。
?
6.5 終止線程的運行
若要終止線程的運行,可以使用下面的方法:
? 線程函數返回(最好使用這種方法) 。
? 通過調用E x i t T h r e a d函數,線程將自行撤消(最好不要使用這種方法) 。
? 同一個進程或另一個進程中的線程調用Te r m i n a t e T h r e a d函數(應該避免使用這種方法) 。
? 包含線程的進程終止運行(應該避免使用這種方法) 。
下面將介紹終止線程運行的方法,并且說明線程終止運行時會出現什么情況。
6.5.1 線程函數返回
????始終都應該將線程設計成這樣的形式,即當想要線程終止運行時,它們就能夠返回。這是確保所有線程資源被正確地清除的唯一辦法。
如果線程能夠返回,就可以確保下列事項的實現:
? 在線程函數中創建的所有C + +對象均將通過它們的撤消函數正確地撤消。
? 操作系統將正確地釋放線程堆棧使用的內存。
? 系統將線程的退出代碼(在線程的內核對象中維護)設置為線程函數的返回值。
? 系統將遞減線程內核對象的使用計數。
6.5.2 ExitThread函數
????可以讓線程調用E x i t T h r e a d函數,以便強制線程終止運行:
VOID ExitThread(DWORD dwExitCode);
該函數將終止線程的運行,并導致操作系統清除該線程使用的所有操作系統資源。但是,C + +資源(如C + +類對象)將不被撤消。由于這個原因,最好從線程函數返回,而不是通過調用E x i t T h r e a d來返回(詳細說明參見第4章) 。
????當然,可以使用E x i t T h r e a d的d w E x i t T h r e a d參數告訴系統將線程的退出代碼設置為什么。E x i t T h r e a d函數并不返回任何值,因為線程已經終止運行,不能執行更多的代碼。
注意 終止線程運行的最佳方法是讓它的線程函數返回。但是,如果使用本節介紹的方法, 應該知道E x i t T h r e a d函數是Wi n d o w s用來撤消線程的函數。如果編寫C / C + +代碼,那么決不應該調用E x i t T h r e a d。應該使用Visual C++運行期庫函數_ e n d t h r e a d e x。如果不使用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的作用和它的重要性。
6.5.3 Te r m i n a t e T h r e a d函數
? ? 調用Te r m i n a t e T h r e a d函數也能夠終止線程的運行:
?
? ? 與E x i t T h r e a d不同,E x i t T h r e a d總是撤消調用的線程, 而Te r m i n a t e T h r e a d能夠撤消任何線程。h T h r e a d參數用于標識被終止運行的線程的句柄。當線程終止運行時,它的退出代碼成為你作為d w E x i t C o d e參數傳遞的值。同時,線程的內核對象的使用計數也被遞減。
? ? 注意 Te r m i n a t e T h r e a d函數是異步運行的函數,也就是說,它告訴系統你想要線程終止運行,但是,當函數返回時,不能保證線程被撤消。如果需要確切地知道該線程已經終止運行,必須調用Wa i t F o r S i n g l e O b j e c t (第9章介紹)或者類似的函數,傳遞線程的句柄。
設計良好的應用程序從來不使用這個函數, 因為被終止運行的線程收不到它被撤消的通知。線程不能正確地清除,并且不能防止自己被撤消。
? ? 注意 當使用返回或調用E x i t T h r e a d的方法撤消線程時,該線程的內存堆棧也被撤消。但是,如果使用Te r m i n a t e T h r e a d,那么在擁有線程的進程終止運行之前,系統不撤消該線程的堆棧。M i c r o s o f t故意用這種方法來實現Te r m i n a t e T h r e a d。如果其他仍然正在執行的線程要引用強制撤消的線程堆棧上的值,那么其他的線程就會出現訪問違規的問題。如果將已經撤消的線程的堆棧留在內存中,那么其他線程就可以繼續很好地運行。
此外,當線程終止運行時, D L L通常接收通知。如果使用 Terminate Thread 強迫線程終止,D L L就不接收通知,這能阻止適當的清除(詳細信息參見第2 0章) 。
6.5.4 在進程終止運行時撤消線程
????第4章介紹的E x i t P r o c e s s和Te r m i n a t e P r o c e s s函數也可以用來終止線程的運行。差別在于這些線程將會使終止運行的進程中的所有線程全部終止運行。另外,由于整個進程已經被關閉,進程使用的所有資源肯定已被清除。這當然包括所有線程的堆棧。這兩個函數會導致進程中的剩余線程被強制撤消,就像從每個剩余的線程調用 Te r m i n a t e T h r e a d一樣。顯然,這意味著正確
的應用程序清除沒有發生,即C + +對象撤消函數沒有被調用,數據沒有轉至磁盤等等。
6.5.5 線程終止運行時發生的操作
?當線程終止運行時,會發生下列操作:
? 線程擁有的所有用戶對象均被釋放。在 Wi n d o w s中,大多數對象是由包含創建這些對象的線程的進程擁有的。但是一個線程擁有兩個用戶對象,即窗口和掛鉤。當線程終止運行時,系統會自動撤消任何窗口,并且卸載線程創建的或安裝的任何掛鉤。其他對象只有在擁有線程的進程終止運行時才被撤消。
? 線程的退出代碼從S T I L L _ A C T I V E改為傳遞給E x i t T h r e a d或Te r m i n a t e T h r e a d的代碼。
? 線程內核對象的狀態變為已通知。
? 如果線程是進程中最后一個活動線程,系統也將進程視為已經終止運行。
? 線程內核對象的使用計數遞減1。當一個線程終止運行時,在與它相關聯的線程內核對象的所有未結束的引用關閉之前,該內核對象不會自動被釋放。
一旦線程不再運行,系統中就沒有別的線程能夠處理該線程的句柄。然而別的線程可以調用G e t E x i t c o d e T h r e a d來檢查由h T h r e a d標識的線程是否已經終止運行。如果它已經終止運行,則確定它的退出代碼:
?
????退出代碼的值在p d w E x i t C o d e指向的D W O R D中返回。如果調用G e t E x i t C o d e T h r e a d時線程尚未終止運行,該函數就用 S T I L L _ A C T I V E標識符(定義為0 x 1 0 3)填入D W O R D。如果該函數運行成功,便返回 T R U E(第9章將詳細地介紹如何使用線程的句柄來確定何時線程終止運行) 。
?
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Windows核心编程 第六章 线程基础知识 (上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Intel汇编程序设计-整数算术指令(下
- 下一篇: Windows核心编程 第六章 线程基础