LINUX作業系統知識:程序與執行緒詳解
當一個程式開始執行後,在開始執行到執行完畢退出這段時間內,它在記憶體中的部分就叫稱作一個程序。
Linux 是一個多工的作業系統,也就是說,在同一時間內,可以有多個程序同時執行。我們大家常用的單CPU計算機實際上在一個時間片段內只能執行一條指令。
那麼Linux是如何實現多程序的同時執行的呢?原來Linux使用了一種稱為” 程序排程 “的手段,首先,為每個程序指派一定的執行時間,這個時間通常很短,短到以毫秒為單位,然後依照某種規則,從眾多的程序中挑選一個投入執行,其他程序暫時等待,當正在執行的那個程序時間耗盡,或者執行完畢退出,或因某種原因暫停,Linux就會重新排程,挑選一個程序投入執行,因為每個程序佔用的時間片段都很短,從使用者的角度來看,就好像多個程序同時執行一樣。
在Linux中,每個程序在建立的時都會被分配一個數據結構,稱為程序控制塊(PCB)。PCB中包含了很多重要的資訊,供系統排程和程序本事執行使用,其中最重要的莫過於程序的ID,程序的ID也被稱為程序標示符,是一個非負的整數,在Linux操作性系統中唯一的標誌一個程序。在最常使用的I386架構上,一個非負的整數的取值是0~32767,這也是我們所可能取到的程序ID,它就是程序的身份證號碼。
殭屍程序的產生
殭屍程序就是已經結束的程序,但是還沒有從程序表中刪除。殭屍程序太多會導致程序表裡麵條目滿了,進而導致系統崩潰,倒是不佔用系統資源。
在程序的狀態中,殭屍程序是非常特殊的一種,它已經放棄了幾乎所有的記憶體空間,沒有任何可執行程式碼,也不能被排程,僅僅在程序列表中保留一個為位置,記載該程序的退出狀態等資訊供其他程序收集,除此之外,殭屍程序不再佔用任何記憶體空間,它需要它的父程序來給它收屍,如果父程序沒安裝
殭屍程序產生的原因:
每個Linux程序在程序表中都有一個進入點(Entry),核心程式在執行該程序時使用到的一切資訊都儲存在進入點。當使用ps命令檢視系統中的程序資訊時,看到的就是程序表中的相關資料。
當fork系統呼叫建立一個新的程序以後,核心程序就會在程序表中給這個新程序分配一個進入點,然後將相關資訊儲存在該進入點所對應的程序表中,這些資訊中有一項是父程序的識別碼。
當這個程序走完了自己的生命週期後,它會執行exit()系統呼叫,此時原來程序表中的資料會被該程序的退出碼、執行時所用的CPU時間等資料所取代,這些資料會一直保留到系統將它傳遞給它的父程序為止。由此可見,殭屍程序的出現時間實在子程式終止後,但是父程序尚未讀取這些資料之前。
如何避免殭屍程序
1、父程序通過wait和waitpid等函式等待子程序結束,這會導致父程序掛起
2、如果父程序很忙,那麼可以用signal函式為SIGCHLD安裝handler,因為子程序結束後,父程序會收到該訊號,可以在handler中呼叫wait回收。
3、如果父程序不關心子程序什麼時候結束,那麼可以用“singal(SIGCHLD),SIG_IGN”通知核心,自己對子程序的結束不感興趣,那麼子程序結束後,核心會回收,並不再給父程序傳送訊號。
4、還有一些技巧,就是fork()兩次,父程序fork一個子程序,然後繼續工作,子程序fork一個孫程序後退出,那麼孫程序被init接管,孫程序結束後,init會回收,不過子程序回收還要自己做。
程序 PK 執行緒
我們先打個比方,多執行緒是十字路口多執行緒是平面交通系統,造價低,但是紅綠燈多,老堵車,而多程序是則是立交橋,雖然造價高,上下坡多耗油,但是不堵車。這是一個抽象的概念。相信大家看完會有這種感覺。
程序和執行緒是兩個相對的概念,通常來說,一個程序可以定義程式的一個例項(Instance)。在Win32中,程序並不執行什麼,它只是佔據應用程式所使用的地址空間。為了讓程序完成一定的工作,程序必須至少佔有一個執行緒,正是這個執行緒負責包含程序地址空間中的程式碼。
實際上,一個程序可以包含幾個執行緒,它們可以同時執行程序地址空間中的程式碼。為了做到這一點,每個執行緒有自己的一組CPU暫存器和堆疊。每個程序中至少有 一個執行緒在執行其地址空間中的程式碼。如果沒有執行緒執行程序地址空間中的程式碼,程序也就沒有繼續存在的理由,系統將自動清除程序及其地址空間。
多執行緒的實現原理
建立一個程序時,它的第一個執行緒稱為主執行緒(Primary thread),由系統自動生成。然後可以由這個主執行緒生成額外的執行緒,而這些執行緒,又可以生成更多的執行緒。在執行一個多執行緒的程式時,從表面上看,這些執行緒似乎在同時執行。而實際情況並非如此,為了執行所有的這些執行緒,作業系統為每個獨立執行緒安排一些CPU時間。</span></p>
單CPU作業系統以時間片輪轉方式向執行緒提供時間片(Quantum),每個執行緒在使用完時間片後交出控制,系統再將CPU時間片分配給下一個執行緒。由於每個時間片足夠的短,這樣就給人一種假象,好像這些執行緒在同時執行。建立額外執行緒的唯一目的就是儘可能地利用CPU時間。
多執行緒的問題
使用多執行緒程式設計可以給程式設計師帶來很大的靈活性,同時也使原來需要複雜技巧才能解決的問題變得容易起來。但是,不應該人為地將編寫的程式分成一些碎片,讓這些碎片按各自的執行緒執行,這不是開發應用程式的正確方法。
執行緒很有用,但當使用執行緒時,可能會在解決老問題的同時產生新問題。例如要開發一個字處理程式,並想讓列印功能作為單獨的執行緒自己執行。這聽起來是很好的主意,因為在列印時,使用者可立即返回,開始編輯文件。
但這樣一來,在該文件被列印時文件中的資料就有可能被修改,列印的結果就不再是所期望的內容。也許最好不要把列印功能放在單獨的執行緒中,不過如果一定要用多執行緒的話,也可以考慮用下面的方法解決:第一種方法是鎖定正在列印的文件,讓使用者編輯其他的文件,這樣在結束列印之前,該文件不會作任何修改;另一個方法可能更有效一些,即可以把該文件拷貝到一個臨時檔案中,列印這個臨時檔案的內容,同時允許使用者對原來的文件進行修改。
當包含文件的臨時檔案列印完成時,再刪去這個臨時檔案。通過上面的分析可以看出,多執行緒在幫助解決問題的同時也可能帶來新問題。因此有必要弄清楚,什麼時候需要建立多執行緒,什麼時候不需要多執行緒。總的來說,多執行緒往往用於在前臺操作的同時還需要進行後臺的計算或邏輯判斷的情況。
執行緒的分類
在MFC中,執行緒被分為兩類,即工作執行緒和使用者介面執行緒。如果一個執行緒只完成後臺計算,不需要和使用者互動,那麼可以使用工作執行緒;如果需要建立一個處理使用者介面的執行緒,則應使用使用者介面執行緒。這兩者的主要區別在於,MFC框架會給使用者介面執行緒增加一個訊息迴圈,這樣使用者介面執行緒就可以處理自己訊息佇列中的訊息。
這樣看來,如果需要在後臺作一些簡單的計算(如對電子表格的重算),則首先應考慮使用工作執行緒,而當 後臺執行緒需要處理比較複雜的任務,確切地說,當後臺執行緒的執行過程會隨著實際情況的不同而改變時,就應該使用使用者介面執行緒,以便能對不同的訊息作出響應。
執行緒的優先順序
當系統需要同時執行多個程序或多個執行緒時,有時會需要指定執行緒的優先順序。執行緒的優先順序一般是指這個執行緒的基優先順序,即執行緒相對於本程序的相對優先順序和包含此執行緒的程序的優先順序的結合。
作業系統以優先順序為基礎安排所有的活動執行緒,系統的每一個執行緒都被分配了一個優先順序,優先順序的範圍從0到31。執行時,系統簡單地給第一個優先順序為31的執行緒分配CPU時間,在該執行緒的時間片結束後,系統給下一個優先順序為31的執行緒分配CPU時間。當沒有優先順序為31的執行緒時,系統將開始給優先順序為30的執行緒分配CPU時間,以此類推。
除了程式設計師在程式中改變執行緒的優先順序外,有時程式在執行過程中系統也會自動地動態改變執行緒的優先順序,這是為了保證系統對終端使用者的高度響應性。比如使用者按了鍵盤上的某個鍵時,系統就會臨時將處理WM_KEYDOWN訊息的執行緒的優先順序提高2到3。CPU按一個完整的時間片執行執行緒,當時間片執行完畢後,系統將該執行緒的優先順序減1。
執行緒的同步
在使用多執行緒程式設計時,還有一個非常重要的問題就是執行緒同步。所謂執行緒同步是指執行緒之間在相互通訊時避免破壞各自資料的能力。同步問題是由前面說到的Win32系統的CPU時間片分配方式引起的。
雖然在某一時刻,只有一個執行緒佔用CPU(單CPU時)時間,但是沒有辦法知道在什麼時候,在什麼地方執行緒被打斷,這樣如何保證執行緒之間不破壞彼此的資料就顯得格外重要。在MFC中,可以使用4個同步物件來保證多執行緒同時執行。它們分別是臨界區物件(CCriticalSection)、互斥量物件(CMutex)、訊號量物件(CS emaphore)和事件物件(CEvent)。
在這些物件中,臨界區物件使用起來最簡單,它的缺點是隻能同步同一個程序中的執行緒。另外,還有一種基本的方法,本文稱為線性化方法,即在程式設計過程中對一定資料的寫操作都在一個執行緒中完成。這樣,由於同一執行緒中的程式碼總是按順序執行的,就不可能出現同時改寫資料的情況。
總結:
線上程中(相對與程序而言),執行緒是一個更加接近執行體的概念,它可以與同進程的其他執行緒共享資料,但擁有自己的棧空間,擁有獨立的執行序列。這兩者都可以提高程式的併發度,提高程式執行的效率和響應的時間。
執行緒和程序在使用上各有優缺點:執行緒執行開銷小,但不利於資源管理和保護;而程序正好相反。根本的區別就一點:用多程序每個程序有自己的地址空間,執行緒則共享地址空間,在速度方面:執行緒產生的速度快,執行緒間的通訊快,切換快等,因為他們在同一地址空間內。
在資源利用率方面:執行緒的資源率比較好也是因為他們在同一地址空間內。 在同步方面:執行緒使用公共變數/記憶體時需要使用同步機制,因為他們在同一地址空間內程序中:子程序是父程序的複製品,子程序獲得父程序資料空間、堆和棧的複製品。