天天看點

核心程式設計6——線程

note 0:

了程序實際是由兩個元件組成的:一個程序核心對象和一個位址空間.類似地,線程也由兩個元件組成:

一個是線程的核心對象,作業系統用它管理線程.核心對象還是系統用來存放線程統計資訊的地方.

一個線程堆棧,用于維護線程執行時所需的所有函數參數和局部變量.

note 1:

程序是有惰性的.程序從來不執行任何東西,它隻是一個線程的容器.線程必然是在某個程序的上下文中建立的,而且會在這個程序内部"終其一生".這意味着線程要在其程序的位址空間内執行代碼和處理資料.此外,這些線程還共享核心對象句柄,因為句柄表是針對每一個程序的,而不是針對每一個線程.相較于線程,程序所使用的系統資源更多.其原因在于位址空間.為一個程序建立一個虛拟的位址空間需要大量系統資源.系統中會發生大量的記錄活動,而這需要用到大量記憶體.而且,由于.exe和.dll檔案要加載到一個位址空間,是以還需要用到檔案資源.另一方面,線程使用的系統資源要少得多.事實上,線程隻有一個核心對象和一個堆棧;幾乎不涉及記錄活動,是以不需要占用多少記憶體.

note 2:

線程描述了程序内部的一個執行路徑.每次初始化程序時,系統都會建立一個主線程.對于用microsoft c/c++編譯器生成的應用程式,這個線程首先會執行c/c++運作庫的啟動代碼,後者調用入口函數(_tmain或_twinmain),并繼續執行,直至入口函數傳回c/c++運作庫的啟動代碼,後者最終将調用exitprocess.對于許多應用程式來說,這個主線程是應用程式惟一需要的線程.但是,程序也可以建立額外的線程來幫助它們完成自己的工作.

note 3:

作業系統的windows indexing services(windows索引服務)建立了一個低優先級的線程.此線程定期醒來.并對硬碟上的特定區域的檔案内容進行索引.windows索引服務極大改進了性能.因為一旦成功建立索引.就不必在每次搜尋時都打開、掃描和關閉硬碟上的每一個檔案.配合這種索引服務.microsoft windows vista提供了一套進階的搜尋功能.

note 4:

如果想建立一個或

多個輔助線程.隻需讓一個正在運作的線程調用createthread:

handle createthread(

psecurity_attributes psa,

dword cbstacksize,

pthread_start_routine pfnstartaddr,

pvoid pvparam,

dword dwcreateflags,

pdword pdwthreadid);

調用createthread時.系統會建立一個線程核心對象.這個線程核心對象不是線程本身.而是一個較小的資料結構.作業系統用這個結構來管理線程.可以把線程核心對象想象為一個由線程統計資訊構成的小型資料結構.這與程序和程序核心對象之間的關系是相同的

系統将程序位址空間的記憶體配置設定給線程堆棧使用.新線程在與負責建立的那個線程相同的程序上下文中運作.是以.新線程可以通路程序核心對象的所有句柄、程序中的所有記憶體以及同一個程序中其他所有線程的堆棧.這樣一來.同一個程序中的多個程序可以很容易地互相通信.

note 5:

createthread函數是用于建立線程的windows函數.不過.如果寫的是c/c++代碼.就絕對不要調用createthread.相反.正确的選擇是使用microsoft c++運作庫函數_beginthreadex.如果使用的不是microsoft c++編譯器.你的編譯器的提供商應該提供類似的函數來替代createthread.不管這個替代函數是什麼.都必須使用它.

note 6:

使用連結器的/stack開關來控制線程堆棧使用多少位址空間.如下所示:

/stack:[reserve] [,commit]

reserve參數用于設定系統将為線程堆棧預留多少位址空間.預設是1 mb(在itanium晶片組上.預設大小為 4 mb).commit參數指定最初應為堆棧的保留區域送出多少實體存儲空間.預設是1頁.随着線程中的代碼開始執行.需要的存儲空間可能不止1頁.如果線程溢出它的堆棧.會産生異常.(有關線程堆棧和堆棧溢出異常的詳情.請參見第16章.有關常見異常處理的詳情.請參見第23章.)系統将捕獲這種異常.并将另一個頁(或者為commit參數指定的任何大小)送出給保留白間.這樣一來.線程堆棧就可以根據需要動态地增大.

note 7:

保留白間的容量設定了堆棧空間的上限.這樣才能捕獲代碼中的無窮遞歸bug.例如.假設你寫了一個函數以遞歸方式調用其自身.而且這個函數存在一個bug.會導緻無窮遞歸.每次此函數調用自身時.都會在記憶體堆棧上建立一個新的堆棧幀.如果系統沒有設定堆棧空間的上限.這個遞歸調用的函數就永遠不會終止調用自身.程序的所有位址空間都會被配置設定出去.大量實體存儲會送出給堆棧.通過設定堆棧空間的上限.可以防止應用程式耗盡實體記憶體區域.而且還可以盡早察覺程式中的bug.

note 8:

建立多個線程時.可以讓它們使用同一個函數位址作為起點.這樣做完全合法.而且非常有用.(可以傳遞了不同的pvparam值,來區分不同的線程所調用相同的函數.)

note 9:

terminatethread函數是異步的.也就是說.它告訴系統你想終止線程.但在函數傳回時.并不保證線程已經終止了.如果需要确定線程已終止運作.還需要調用waitforsingleobject或類似的函數.并向其傳遞線程的句柄.

note 10:

如果通過傳回或調用exitthread函數的方式來終止一個線程的運作.該線程的堆棧也會被銷毀.但是.如果使用的是terminatethread.那麼除非擁有此線程的程序終止運作.否則系統不會銷毀這個線程的堆棧.microsoft故意以這種方式來實作terminatethread.否則.假如其他還在運作的線程要引用被"殺死"的那個線程的堆棧上的值.就會引起通路沖突.讓被"殺死"的線程的堆棧保留在記憶體中.其他的線程就可以繼續正常運作.

此外.動态連結庫(dll)通常會線上程終止運作時收到通知.不過.如果線程是用terminatethread強行"殺死"的.則dll不會收到這個通知.其結果是不能執行正常的清理工作.

note 11:

線程終止運作時.會發生下面這些事情:

線程擁有的所有使用者對象句柄會被釋放.在windows中.大多數對象都是由包含了"建立這些對象的線程"的程序擁有的.但是.一個線程有兩個user對象:視窗(window)和挂鈎(hook).一個線程終止運作時.系統會自動銷毀由線程建立或安裝的任何視窗.并解除安裝由線程建立或安裝的任何挂鈎.其他對象隻有在擁有線程的程序終止時才被銷毀.

線程的退出代碼從still_active變成傳給exitthread或terminatethread的代碼.

線程核心對象的狀态變為signaled.

如果線程是程序中的最後一個活動線程.系統認為程序也終止了.

線程核心對象的使用計數遞減1.

note 19:

對createthread函數的一個調用導緻系統建立一個線程核心對象.該對象最初的使用計數為2.(除非線程終止,而且從createthread傳回的句柄關閉,否則線程核心對象不會被銷毀.)該線程核心對象的其他屬性也被初始化:暫停計數被設為1,退出代碼被設為still_active (0x103),而且對象被設為nonsignaled狀态.

note 20:

一旦建立了核心對象,系統就配置設定記憶體,供線程的堆棧使用.此記憶體是從程序的位址空間内配置設定的,因為線程沒有自己的位址空間.然後,系統将兩個值寫入新線程堆棧的最上端.(線程堆棧始終是從高位記憶體位址向低位記憶體位址建構的.)寫入線程堆棧的第一個值是傳給createthread函數的pvparam參數的值.緊接在它下方的是傳給createthread函數的pfnstartaddr值.

note 21:

每個線程都有其自己的一組cpu寄存器,稱為線程的上下文(context).上下文反映了當線程上一次執行時,線程的cpu寄存器的狀态.線程的cpu寄存器全部儲存在一個context結構(在winnt.h頭檔案中定義).context結構本身儲存線上程核心對象中.

note 22:

當線程的核心對象被初始化的時候,context結構的堆棧指針寄存器被設為pfnstartaddr線上程堆棧中的位址.而指令指針寄存器被設為rtluserthreadstart函數(該函數未見于正式文檔)的位址,此函數是ntdll.dll子產品導出的.

rtluserthreadstart函數的基本用法如下:

void rtluserthreadstart(pthread_start_routine pfnstartaddr, pvoid pvparam) {

__try {

exitthread((pfnstartaddr)(pvparam));

}

__except(unhandledexceptionfilter(getexceptioninformation())) {

exitprocess(getexceptioncode());

// note: we never get here.

note 23:

線程完全初始化好之後,系統将檢查create_suspended标志是否傳給createthread函數.如果此标記沒有傳遞,系統将線程的暫停計數遞增至0;随後,線程就可以排程給一個處理器去執行.然後,系統在實際的cpu寄存器中加載上一次線上程上下文中儲存的值.現在,線程可以在其程序的位址空間中執行代碼并處理資料了.

note 24:

新線程執行rtluserthreadstart函數的時候,将發生以下事情:

圍繞你的線程函數,會設定一個結構化異常處理(structured exception handling,seh)幀.這樣一來,線程執行期間所産生的任何異常都能得到系統的預設處理.

系統調用你的線程函數,把你傳給createthread函數的pvparam參數傳給它.

線程函數傳回時,rtluserthreadstart調用exitthread,将你的線程函數的傳回值傳給它.線程核心對象的使用計數遞減,而後線程停止執行.

如果線程産生了一個未被處理的異常,rtluserthreadstart函數所設定的seh幀會處理這個異常.通常,這意味着會向使用者顯示一個消息框,而且當使用者關閉此消息框時,rtluserthreadstart會調用exitprocess來終止整個程序,而不隻是終止有問題的線程.

note 25:

當rtluserthreadstart開始執行時,它會調用c/c++運作庫的啟動代碼,後者初始化繼而調用你的_tmain或_twinmain函數.你的入口函數傳回時,c/c++運作時啟動代碼會調用exitprocess.是以對于c/c++應用程式來說,主線程永遠不會傳回到rtluserthreadstart函數.

note 26:

visual studio附帶了4個原生的c/c++運作庫,還有2個庫面向microsoft.net的托管環境.注意,所有這些庫都支援多線程開發:不再有單獨的一個c/c++庫專門針對單線程開發.下面對這些庫進行了描述.

microsoft visual studio附帶的c/c++庫

庫名稱      描述

libcmt.lib   庫的靜态連結release版本

libcmtd.lib 庫的靜态連結debug版本

msvcrt.lib   導入庫,用于動态連結msvcr80.dll 庫的release版本. (這是建立項目時的預設庫)

msvcrtd.lib 導入庫,用于動态連結msvcr80d.dll庫的debug版本

msvcmrt.lib 導入庫,用于托管/原生代碼混合

msvcurt.lib 導入庫,編譯成百分之百純msil代碼

-------------------------------------------------------------------