線程

線程狀態
在一個線程的生存期內,可以在多種狀態之間轉換。不同操作系統可以實現不同的線程模型,定義許多不同的線程狀態,每個狀態還可以包含多個子狀態。但大體說來,如下幾種狀態是通用的:就緒:參與調度,等待被執行。一旦被調度選中,立即開始執行。運行:佔用CPU,正在運行中。休眠:暫不參與調度,等待特定事件發生。中止:已經運行完畢,等待回收線程資源(要注意,這個很容易誤解,後面解釋)。
線程環境
線程存在於進程之中。進程內所有全局資源對於內部每個線程均是可見的。
進程內典型全局資源有如下幾種:
       代碼區。這意味着當前進程空間內所有可見的函數代碼,對於每個線程來說也是可見的。
       靜態存儲區。全局變量。靜態變量。
       動態存儲區。也就是堆空間。
線程內典型的局部資源有:
       本地棧空間。存放本線程的函數調用棧,函數內部的局部變量等。
       部分寄存器變量。例如本線程下一步要執行代碼的指針偏移量。

一個進程發起之後,會首先生成一個缺省的線程,通常稱這個線程爲主線程。C/C++程序中主線程就是通過main函數進入的線程

主線程衍生的線程稱爲從線程,從線程也可以有自己的入口函數,作用相當於主線程的main函數。

這個函數由用戶指定。Pthread和winapi中都是通過傳入函數指針實現。在指定線程入口函數時,也可以指定入口函數的參數。

就像main函數有固定的格式要求一樣,線程的入口函數一般也有固定的格式要求,參數通常都是void*類型,返回類型在

pthread中是void *, winapi中是unsigned int,而且都需要是全局函數。

最常見的線程模型中,除主線程較爲特殊之外,其他線程一旦被創建,相互之間就是對等關係 (peer to peer), 不存在隱含的層次關係。每個進程可以創建的最大線程數由具體實現決定。

無論在windows中,還是Posix中,主線程和子線程的默認關係是:
無論子線程執行完畢與否,一旦主線程執行完畢退出,所有子線程執行都會終止。這時整個進程結束或僵死(部分線程保持一種終止執行但還未銷燬的狀態,而進程必須在其所有線程銷燬後銷燬,這時進程處於僵死狀態),在第一個例子的輸出中,可以看到子線程還來不及執行完畢,主線程的main()函數就已經執行完畢,從而所有子線程終止。

需要強調的是,線程函數執行完畢退出,或以其他非常方式終止,線程進入終止態(請回顧上面說的線程狀態),但千萬要記住的是,進入終止態後,爲線程分配的系統資源並不一定已經釋放,而且可能在系統重啓之前,一直都不能釋放。終止態的線程,仍舊作爲一個線程實體存在與操作系統中。(這點在win和unix中是一致的。)而什麼時候銷燬線程,取決於線程屬性。

通常,這種終止方式並非我們所期望的結果,而且一個潛在的問題是未執行完就終止的子線程,除了作爲線程實體佔用系統資源之外,其線程函數所擁有的資源(申請的動態內存,打開的文件,打開的網絡端口等)也不一定能釋放。所以,針對這個問題,主線程和子線程之間通常定義兩種關係:
  可會合(joinable)。這種關係下,主線程需要明確執行等待操作。在子線程結束後,主線程的等待操作執行完畢,子線程和主線程會合。這時主線程繼續執行等待操作之後的下一步操作。主線程必須會合可會合的子線程,Thread類中,這個操作通過在主線程的線程函數內部調用子線程對象的wait()函數實現。這也就是上面加上三個wait()調用後顯示正確的原因。必須強調的是,即使子線程能夠在主線程之前執行完畢,進入終止態,也必需顯示執行會合操作,否則,系統永遠不會主動銷燬線程,分配給該線程的系統資源(線程id或句柄,線程管理相關的系統資源)也永遠不會釋放。
  相分離(detached)。顧名思義,這表示子線程無需和主線程會合,也就是相分離的。這種情況下,子線程一旦進入終止態,系統立即銷燬線程,回收資源。無需在主線程內調用wait()實現會合。Thread類中,調用detach()使線程進入detached狀態。

這種方式常用在線程數較多的情況,有時讓主線程逐個等待子線程結束,或者讓主線程安排每個子線程結束的等待順序,是很困難或者不可能的。所以在併發子線程較多的情況下,這種方式也會經常使用。
缺省情況下,創建的線程都是可會合的。可會合的線程可以通過調用detach()方法變成相分離的線程。但反向則不行。

常遇到的問題是:
  內存泄漏,系統資源泄漏。
  程序執行結果混亂,但是在某些點插入sleep語句後結果又正確了。
  程序crash, 但移除或添加部分無關語句後,整個程序正常運行(假相)。
  多線程程序執行結果完全不合邏輯,出於預期。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章