程序和執行緒的概念
先了解一下作業系統的一些相關概念,大部分作業系統(如Windows、Linux)的任務排程是採用時間片輪轉的搶佔式排程方式,也就是說一個任務執行一小段時間後強制暫停去執行下一個任務,每個任務輪流執行。任務執行的一小段時間叫做時間片,任務正在執行時的狀態叫執行狀態,任務執行一段時間後強制暫停去執行下一個任務,被暫停的任務就處於就緒狀態等待下一個屬於它的時間片的到來。這樣每個任務都能得到執行,由於CPU的執行效率非常高,時間片非常短,在各個任務之間快速地切換,給人的感覺就是多個任務在“同時進行”,這也就是我們所說的併發(併發簡單來說多個任務同時執行)。
程序
計算機的核心是CPU,它承擔了所有的計算任務;而作業系統是計算機的管理者,它負責任務的排程、資源的分配和管理,統領整個計算機硬體;應用程式側是具有某種功能的程式,程式是運行於作業系統之上的。
程序是一個具有一定獨立功能的程式在一個數據集上的一次動態執行的過程,是作業系統進行資源分配和排程的一個獨立單位,是應用程式執行的載體。程序是一種抽象的概念,從來沒有統一的標準定義。程序一般由程式、資料集合和程序控制塊三部分組成。程式用於描述程序要完成的功能,是控制程序執行的指令集;資料集合是程式在執行時所需要的資料和工作區;程式控制塊(Program Control Block,簡稱PCB),包含程序的描述資訊和控制資訊,是程序存在的唯一標誌。
程序具有的特徵:
動態性:程序是程式的一次執行過程,是臨時的,有生命期的,是動態產生,動態消亡的;
併發性:任何程序都可以同其他程序一起併發執行;
獨立性:程序是系統進行資源分配和排程的一個獨立單位;
結構性:程序由程式、資料和程序控制塊三部分組成。
程序的生命週期
在早期只有程序的作業系統中,程序有五種狀態,建立、就緒、執行、阻塞(等待)、退出。
建立:程序正在建立,還不能執行。作業系統在建立程序時要進行的工作包括分配和建立程序控制塊表項、建立資源表格並分配資源、載入程式並建立地址空間;
就緒:時間片已用完,此執行緒被強制暫停,等待下一個屬於他的時間片到來;
執行:此執行緒正在執行,正在佔用時間片;
阻塞:也叫等待狀態,等待某一事件(如IO或另一個執行緒)執行完;
退出:程序已結束,所以也稱結束狀態,釋放作業系統分配的資源。
執行緒
在早期的作業系統中並沒有執行緒的概念,程序是能擁有資源和獨立執行的最小單位,也是程式執行的最小單位。任務排程採用的是時間片輪轉的搶佔式排程方式,而程序是任務排程的最小單位,每個程序有各自獨立的一塊記憶體,使得各個程序之間記憶體地址相互隔離。
後來,隨著計算機的發展,對CPU的要求越來越高,程序之間的切換開銷較大,已經無法滿足越來越複雜的程式的要求了。於是就發明了執行緒,執行緒是程式執行中一個單一的順序控制流程,是程式執行流的最小單元,是處理器排程和分派的基本單位。一個程序可以有一個或多個執行緒,各個執行緒之間共享程式的記憶體空間。
一個標準的執行緒由執行緒ID、當前指令指標(PC)、暫存器和堆疊組成。而程序由記憶體空間(程式碼、資料、程序空間、開啟的檔案)和一個或多個執行緒組成。
執行緒的生命週期
當執行緒的數量小於處理器的數量時,執行緒的併發是真正的併發,不同的執行緒執行在不同的處理器上。但當執行緒的數量大於處理器的數量時,執行緒的併發會受到一些阻礙,此時並不是真正的併發,因為此時至少有一個處理器會執行多個執行緒。在單個處理器執行多個執行緒時,併發是一種模擬出來的狀態。作業系統採用時間片輪轉的方式輪流執行每一個執行緒。現在,幾乎所有的現代作業系統採用的都是時間片輪轉的搶佔式排程方式,如我們熟悉的Unix、linux、Windows及Mac OS X等流行的作業系統。
建立:一個新的執行緒被建立,等待該執行緒被呼叫執行;
就緒:時間片已用完,此執行緒被強制暫停,等待下一個屬於他的時間片到來;
執行:此執行緒正在執行,正在佔用時間片;
阻塞:也叫等待狀態,等待某一事件(如IO或另一個執行緒)執行完;
退出:一個執行緒完成任務或者其他終止條件發生,該執行緒終止進入退出狀態,退出狀態釋放該執行緒所分配的資源。
執行緒優先順序
作業系統(如Windows、Linux、Mac OS X)的任務排程除了具有前面提到的時間片輪轉的特點外,還有優先順序排程(Priority Schedule)的特點。優先順序排程決定了執行緒按照什麼順序輪流執行,在具有優先順序排程的系統中,執行緒擁有各自的執行緒優先順序(Thread Priority)。具有高優先順序的執行緒會更早地執行,而低優先順序的執行緒通常要等沒有更高優先順序的可執行執行緒時才會被執行。
執行緒的優先順序可以由使用者手動設定,此外系統也會根據不同情形調整優先順序。通常情況下,頻繁地進入等待狀態(進入等待狀態會放棄之前仍可佔用的時間份額)的執行緒(如IO執行緒),比頻繁進行大量計算以至於每次都把所有時間片全部用盡的執行緒更受作業系統的歡迎。因為頻繁進入等待的執行緒只會佔用很少的時間,這樣作業系統可以處理更多的任務。我們把頻繁等待的執行緒稱之為IO密集型執行緒(IO Bound Thread),而把很少等待的執行緒稱之為CPU密集型執行緒(CPU Bound Thread)。IO密集型執行緒總是比CPU密集型執行緒更容易得到優先順序的提升。
執行緒餓死
在優先順序排程下,容易出現一種執行緒餓死的現象。一個執行緒餓死是說它的優先順序較低,在它執行之前總是有比它優先順序更高的執行緒等待執行,因此這個低優先順序的執行緒始終得不到執行。當CPU密集型的執行緒優先順序較高時,其它低優先順序的執行緒就很可能出現餓死的情況;當IO密集型執行緒優先順序較高時,其它執行緒相對不容易造成餓死的,因為IO執行緒有大量的等待時間。為了避免執行緒餓死,排程系統通常會逐步提升那些等待了很久而得不到執行的執行緒的優先順序。這樣,一個執行緒只要它等待了足夠長的時間,其優先順序總會被提升到可以讓它執行的程度,也就是說這種情況下執行緒始終會得到執行,只是時間的問題。
在優先順序排程環境下,執行緒優先順序的改變有三種方式:
1.使用者指定優先順序;
2.根據進入等待狀態的頻繁程度提升或降低優先順序(由作業系統完成);
3.長時間得不到執行而被提升優先順序。
多執行緒與多核
上面提到的時間片輪轉的排程方式說一個任務執行一小段時間後強制暫停去執行下一個任務,每個任務輪流執行。很多作業系統的書都說“同一時間點只有一個任務在執行”。其實“同一時間點只有一個任務在執行”這句話是不準確的,至少它是不全面的。我們分析一下多核的情況。
這是我的電腦的CPU情況圖:
多核(心)處理器是指在一個處理器上整合多個運算核心從而提高計算能力,也就是有多個真正平行計算的處理核心,每一個處理核心對應一個核心執行緒。核心執行緒(Kernel Thread, KLT)就是直接由作業系統核心支援的執行緒,這種執行緒由核心來完成執行緒切換,核心透過操作排程器對執行緒進行排程,並負責將執行緒的任務對映到各個處理器上。一般一個處理核心對應一個核心執行緒,比如單核處理器對應一個核心執行緒,雙核處理器對應兩個核心執行緒,四核處理器對應四個核心執行緒。
現在的電腦一般是雙核四執行緒、四核八執行緒,是採用超執行緒技術將一個物理處理核心模擬成兩個邏輯處理核心,對應兩個核心執行緒,所以在作業系統中看到的CPU數量是實際物理CPU數量的兩倍。但是我的如上圖是四核四執行緒,似乎沒有用這個超執行緒技術。
超執行緒技術就是利用特殊的硬體指令,把一個物理晶片模擬成兩個邏輯處理核心,讓單個處理器都能使用執行緒級平行計算,進而相容多執行緒作業系統和軟體,減少了CPU的閒置時間,提高的CPU的執行效率。這種超執行緒技術(如雙核四執行緒)由處理器硬體的決定,同時也需要作業系統的支援才能在計算機中表現出來。
程式一般不會直接去使用核心執行緒,而是去使用核心執行緒的一種高階介面——輕量級程序(Light Weight Process,LWP),輕量級程序就是我們通常意義上所講的執行緒(我們在這稱它為使用者執行緒),由於每個輕量級程序都由一個核心執行緒支援,因此只有先支援核心執行緒,才能有輕量級程序。使用者執行緒與核心執行緒的對應關係有三種模型:一對一模型、多對一模型、多對多模型,在這以4個核心執行緒、3個使用者執行緒為例對三種模型進行說明。
一對一模型
對於一對一模型來說,一個使用者執行緒就唯一地對應一個核心執行緒(反過來不一定成立,一個核心執行緒不一定有對應的使用者執行緒)。這樣,如果CPU沒有采用超執行緒技術(如四核四執行緒的計算機,就如上圖展示的我使用的計算機),一個使用者執行緒就唯一地對映到一個物理CPU的執行緒,執行緒之間的併發是真正的併發。一對一模型使使用者執行緒具有與核心執行緒一樣的優點,一個執行緒因某種原因阻塞時其他執行緒的執行不受影響;此處,一對一模型也可以讓多執行緒程式在多處理器的系統上有更好的表現。但一對一模型也有兩個缺點:
1.許多作業系統限制了核心執行緒的數量,因此一對一模型會使使用者執行緒的數量受到限制;
2.許多作業系統核心執行緒排程時,上下文切換的開銷較大,導致使用者執行緒的執行效率下降。
多對一模型
多對一模型將多個使用者執行緒對映到一個核心執行緒上,執行緒之間的切換由使用者態的程式碼來進行,因此相對一對一模型,多對一模型的執行緒切換速度要快許多;此外,多對一模型對使用者執行緒的數量幾乎無限制。但多對一模型也有兩個缺點:
1.如果其中一個使用者執行緒阻塞,那麼其它所有執行緒都將無法執行,因為此時核心執行緒也隨之阻塞了;
2.在多處理器系統上,處理器數量的增加對多對一模型的執行緒效能不會有明顯的增加,因為所有的使用者執行緒都對映到一個處理器上了。
多對多模型
多對多模型結合了一對一模型和多對一模型的優點,將多個使用者執行緒對映到多個核心執行緒上。多對多模型的優點有:
1.一個使用者執行緒的阻塞不會導致所有執行緒的阻塞,因為此時還有別的核心執行緒被排程來執行;
2.多對多模型對使用者執行緒的數量沒有限制;
3.在多處理器的作業系統中,多對多模型的執行緒也能得到一定的效能提升,但提升的幅度不如一對一模型的高。
程序與執行緒的區別
執行緒是程式執行的最小單位,而程序是作業系統分配資源的最小單位;一個程序由一個或多個執行緒組成,執行緒是一個程序中程式碼的不同執行路線;程序之間相互獨立,但同一程序下的各個執行緒之間共享程式的記憶體空間(包括程式碼段、資料集、堆等)及一些程序級的資源(如開啟檔案和訊號),某程序內的執行緒在其它程序不可見;排程和切換:執行緒上下文切換比程序上下文切換要快得多。總之,執行緒和程序都是一種抽象的概念,執行緒是一種比程序更小的抽象,執行緒和程序都可用於實現併發。在早期的作業系統中並沒有執行緒的概念,程序是能擁有資源和獨立執行的最小單位,也是程式執行的最小單位。它相當於一個程序裡只有一個執行緒,程序本身就是執行緒。所以執行緒有時被稱為輕量級程序(Lightweight Process,LWP)。
後來,隨著計算機的發展,對多個任務之間上下文切換的效率要求越來越高,就抽象出一個更小的概念——執行緒,一般一個程序會有多個(也可是一個)執行緒。
漫話程序和執行緒
1.計算機的核心是CPU,它承擔了所有的計算任務。它就像一座工廠,時刻在執行。
2.假定工廠的電力有限,一次只能供給一個車間使用。也就是說,一個車間開工的時候,其他車間都必須停工。背後的含義就是,單個CPU一次只能執行一個任務。
3.程序就好比工廠的車間,它代表CPU所能處理的單個任務。任一時刻,CPU總是執行一個程序,其他程序處於非執行狀態。
4.一個車間裡,可以有很多工人。他們協同完成一個任務。
5.執行緒就好比車間裡的工人。一個程序可以包括多個執行緒。
6.車間的空間是工人們共享的,比如許多房間是每個工人都可以進出的。這象徵一個程序的記憶體空間是共享的,每個執行緒都可以使用這些共享記憶體。
7.可是,每間房間的大小不同,有些房間最多隻能容納一個人,比如廁所。裡面有人的時候,其他人就不能進去了。這代表一個執行緒使用某些共享記憶體時,其他執行緒必須等它結束,才能使用這一塊記憶體。
8.一個防止他人進入的簡單方法,就是門口加一把鎖。先到的人鎖上門,後到的人看到上鎖,就在門口排隊,等鎖開啟再進去。這就叫”互斥鎖”(Mutual exclusion,縮寫 Mutex),防止多個執行緒同時讀寫某一塊記憶體區域。
9.還有些房間,可以同時容納n個人,比如廚房。也就是說,如果人數大於n,多出來的人只能在外面等著。這好比某些記憶體區域,只能供給固定數目的執行緒使用。
10.這時的解決方法,就是在門口掛n把鑰匙。進去的人就取一把鑰匙,出來時再把鑰匙掛回原處。後到的人發現鑰匙架空了,就知道必須在門口排隊等著了。這種做法叫做”訊號量”(Semaphore),用來保證多個執行緒不會互相沖突。
不難看出,mutex是semaphore的一種特殊情況(n=1時)。也就是說,完全可以用後者替代前者。但是,因為mutex較為簡單,且效率高,所以在必須保證資源獨佔的情況下,還是採用這種設計。
作業系統的設計,因此可以歸結為三點:
(1)以多程序形式,允許多個任務同時執行;
(2)以多執行緒形式,允許單個任務分成不同的部分執行;
(3)提供協調機制,一方面防止程序之間和執行緒之間產生衝突,另一方面允許程序之間和執行緒之間共享資源。