標籤:
Linux 線程介紹進程與線程
典型的UNIX/Linux進程可以看成只有一個控制線程:一個進程在同一時刻只做一件事情。有了多個控制線程後,在程式設計時可以把進程設計成在同一時刻做不止一件事,每個線程各自處理獨立的任務。
進程是程式執行時的一個執行個體,是擔當分配系統資源(CPU時間、記憶體等)的基本單位。在面向線程設計的系統中,進程本身不是基本運行單位,而是線程的容器。程式本身只是指令、資料及其組織形式的描述,進程才是程式(那些指令和資料)的真正運行執行個體。
線程是作業系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流程,一個進程中可以並發多個線程,每條線程並存執行不同的任務。線程包含了表示進程內執行環境必須的資訊,其中包括進程中表示線程的線程ID、一組寄存器值、棧、調度優先順序和策略、訊號屏蔽字、errno常量以及線程私人資料。進程的所有資訊對該進程的所有線程都是共用的,包括可執行檔程式文本、程式的全域記憶體和對記憶體、棧以及檔案描述符。在Unix和類Unix作業系統中線程也被稱為輕量級進程(lightweight processes),但輕量級進程更多指的是核心線程(kernel thread),而把使用者線程(user thread)稱為線程。
"進程——資源分派的最小單位,線程——程式執行的最小單位"
進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變數,但線程沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程式要比多線程的程式健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共用某些變數的並行作業,只能用線程,不能用進程。
從函數調用上來說,進程建立使用fork()操作;線程建立使用clone()操作。Richard Stevens大師這樣說過:
fork is expensive. Memory is copied from the parent to the child, all descriptors are duplicated in the child, and so on. Current implementations use a technique called copy-on-write, which avoids a copy of the parent‘s data space to the child until the child needs its own copy. But, regardless of this optimization,fork is expensive.
IPC is required to pass information between the parent and child after the fork. Passing information from the parent to the child before the fork is easy, since the child starts with a copy of the parent‘s data space and with a copy of all the parent‘s descriptors. But, returning information from the child to the parent takes more work.
Threads help with both problems. Threads are sometimes called lightweight processes since a thread is "lighter weight" than a process. That is, thread creation can be 10–100 times faster than process creation.
All threads within a process share the same global memory. This makes the sharing of information easy between the threads, but along with this simplicity comes the problem of synchronization.
使用線程的理由
(本部分摘自Linux多線程編程(不限Linux))
從上面我們知道了進程與線程的區別,其實這些區別也就是我們使用線程的理由。總的來說就是:進程有獨立的地址空間,線程沒有單獨的地址空間(同一進程內的線程共用進程的地址空間)。
使用多線程的理由之一是和進程相比,它是一種非常"節儉"的多任務操作方式。我們知道,在Linux系統下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的資料表來維護它的程式碼片段、堆棧段和資料區段,這是一種"昂貴"的多任務工作方式。而運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共用大部分資料,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。據統計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統上,這個資料可能會有較大的區別。
使用多線程的理由之二是線程間方便的通訊機制。對不同進程來說,它們具有獨立的資料空間,要進行資料的傳遞只能通過通訊的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由於同一進程下的線程之間共用資料空間,所以一個線程的資料可以直接為其它線程所用,這不僅快捷,而且方便。當然,資料的共用也帶來其他一些問題,有的變數不能同時被兩個線程所修改,有的子程式中聲明為static的資料更有可能給多線程程式帶來災難性的打擊,這些正是編寫多線程程式時最需要注意的地方。
除了以上所說的優點外,不和進程比較,多線程程式作為一種多任務、並發的工作方式,當然有以下的優點:
- 提高應用程式響應。這對圖形介面的程式尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程式不會響應鍵盤、滑鼠、菜單的操作,而使用多線程技術,將耗時間長度的操作(time consuming)置於一個新的線程,可以避免這種尷尬的情況。
- 使多CPU系統更加有效。作業系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。
- 改善程式結構。一個既長又複雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程式會利於理解和修改。
Linux上線程開發API概要
多線程開發在 Linux 平台上已經有成熟的 pthread 庫支援。其涉及的多線程開發的最基本概念主要包含三點:線程,互斥鎖,條件。其中,線程操作又分線程的建立,退出,等待 3 種。互斥鎖則包括 4 種操作,分別是建立,銷毀,加鎖和解鎖。條件操作有 5 種操作:建立,銷毀,觸發,廣播和等待。其他的一些線程擴充概念,如號誌等,都可以通過上面的三個基本元素的基本操作封裝出來。詳細請見下表:
參考資料
Linux多線程編程初探