什麼是線程?線程與程序與有什麼關系?這是一個非常抽象的問題,也是一個特别廣的話題,涉及到非常多的知識。我不能確定能把它講的話,也不能確定講的内容全部都正确。即使這樣,我也希望盡可能地把他講通俗一點,講的明白一點,因為這是個一直困擾我很久的,撲朔迷離的知識領域,希望通過我的了解揭開它一層一層神秘的面紗。
線程是什麼?要了解這個概念,須要先了解一下作業系統的一些相關概念。大部分作業系統(如Windows、Linux)的任務排程是采用時間片輪轉的搶占式排程方式,也就是說一個任務執行一小段時間後強制暫停去執行下一個任務,每個任務輪流執行。任務執行的一小段時間叫做時間片,任務正在執行時的狀态叫運作狀态,任務執行一段時間後強制暫停去執行下一個任務,被暫停的任務就處于就緒狀态等待下一個屬于它的時間片的到來。這樣每個任務都能得到執行,由于CPU的執行效率非常高,時間片非常短,在各個任務之間快速地切換,給人的感覺就是多個任務在“同時進行”,這也就是我們所說的并發(别覺得并發有多高深,它的實作很複雜,但它的概念很簡單,就是一句話:多個任務同時執行)。多任務運作過程的示意圖如下:

圖 1:作業系統中的任務排程
我們都知道計算機的核心是CPU,它承擔了所有的計算任務;而作業系統是計算機的管理者,它負責任務的排程、資源的配置設定和管理,統領整個計算機硬體;應用程式側是具有某種功能的程式,程式是運作于作業系統之上的。
程序是一個具有一定獨立功能的程式在一個資料集上的一次動态執行的過程,是作業系統進行資源配置設定和排程的一個獨立機關,是應用程式運作的載體。程序是一種抽象的概念,從來沒有統一的标準定義。程序一般由程式、資料集合和程序控制塊三部分組成。程式用于描述程序要完成的功能,是控制程序執行的指令集;資料集合是程式在執行時所需要的資料和工作區;程式控制塊(Program Control Block,簡稱PCB),包含程序的描述資訊和控制資訊,是程序存在的唯一标志。
程序具有的特征:
動态性:程序是程式的一次執行過程,是臨時的,有生命期的,是動态産生,動态消亡的;
并發性:任何程序都可以同其他程序一起并發執行;
獨立性:程序是系統進行資源配置設定和排程的一個獨立機關;
結構性:程序由程式、資料和程序控制塊三部分組成。
在早期的作業系統中并沒有線程的概念,程序是能擁有資源和獨立運作的最小機關,也是程式執行的最小機關。任務排程采用的是時間片輪轉的搶占式排程方式,而程序是任務排程的最小機關,每個程序有各自獨立的一塊記憶體,使得各個程序之間記憶體位址互相隔離。
後來,随着計算機的發展,對CPU的要求越來越高,程序之間的切換開銷較大,已經無法滿足越來越複雜的程式的要求了。于是就發明了線程,線程是程式執行中一個單一的順序控制流程,是程式執行流的最小單元,是處理器排程和分派的基本機關。一個程序可以有一個或多個線程,各個線程之間共享程式的記憶體空間(也就是所在程序的記憶體空間)。一個标準的線程由線程ID、目前指令指針(PC)、寄存器和堆棧組成。而程序由記憶體空間(代碼、資料、程序空間、打開的檔案)和一個或多個線程組成。
前面講了程序與線程,但可能你還覺得迷糊,感覺他們很類似。的确,程序與線程有着千絲萬縷的關系,下面就讓我們一起來理一理:
1.線程是程式執行的最小機關,而程序是作業系統配置設定資源的最小機關;
2.一個程序由一個或多個線程組成,線程是一個程序中代碼的不同執行路線;
3.程序之間互相獨立,但同一程序下的各個線程之間共享程式的記憶體空間(包括代碼段、資料集、堆等)及一些程序級的資源(如打開檔案和信号),某程序内的線程在其它程序不可見;
4.排程和切換:線程上下文切換比程序上下文切換要快得多。
線程與程序關系的示意圖:
圖 2:程序與線程的資源共享關系
圖 3:單線程與多線程的關系
總之,線程和程序都是一種抽象的概念,線程是一種比程序更小的抽象,線程和程序都可用于實作并發。
在早期的作業系統中并沒有線程的概念,程序是能擁有資源和獨立運作的最小機關,也是程式執行的最小機關。它相當于一個程序裡隻有一個線程,程序本身就是線程。是以線程有時被稱為輕量級程序(Lightweight Process,LWP)。
圖 4:早期的作業系統隻有程序,沒有線程
後來,随着計算機的發展,對多個任務之間上下文切換的效率要求越來越高,就抽象出一個更小的概念——線程,一般一個程序會有多個(也可是一個)線程。
圖 5:線程的出現,使得一個程序可以有多個線程
上面提到的時間片輪轉的排程方式說一個任務執行一小段時間後強制暫停去執行下一個任務,每個任務輪流執行。很多作業系統的書都說“同一時間點隻有一個任務在執行”。那有人可能就要問雙核處理器呢?難道兩個核不是同時運作嗎?
其實“同一時間點隻有一個任務在執行”這句話是不準确的,至少它是不全面的。那多核處理器的情況下,線程是怎樣執行呢?這就需要了解核心線程。
多核(心)處理器是指在一個處理器上內建多個運算核心進而提高計算能力,也就是有多個真正并行計算的處理核心,每一個處理核心對應一個核心線程。核心線程(Kernel Thread, KLT)就是直接由作業系統核心支援的線程,這種線程由核心來完成線程切換,核心通過操作排程器對線程進行排程,并負責将線程的任務映射到各個處理器上。一般一個處理核心對應一個核心線程,比如單核處理器對應一個核心線程,雙核處理器對應兩個核心線程,四核處理器對應四個核心線程。
現在的電腦一般是雙核四線程、四核八線程,是采用超線程技術将一個實體處理核心模拟成兩個邏輯處理核心,對應兩個核心線程,是以在作業系統中看到的CPU數量是實際實體CPU數量的兩倍,如你的電腦是雙核四線程,打開“任務管理器\性能”可以看到4個CPU的螢幕,四核八線程可以看到8個CPU的螢幕。
圖 6:雙核四線程在Windows8下檢視的結果
超線程技術就是利用特殊的硬體指令,把一個實體晶片模拟成兩個邏輯處理核心,讓單個處理器都能使用線程級并行計算,進而相容多線程作業系統和軟體,減少了CPU的閑置時間,提高的CPU的運作效率。這種超線程技術(如雙核四線程)由處理器硬體的決定,同時也需要作業系統的支援才能在計算機中表現出來。
程式一般不會直接去使用核心線程,而是去使用核心線程的一種進階接口——輕量級程序(Light Weight Process,LWP),輕量級程序就是我們通常意義上所講的線程(我們在這稱它為使用者線程),由于每個輕量級程序都由一個核心線程支援,是以隻有先支援核心線程,才能有輕量級程序。使用者線程與核心線程的對應關系有三種模型:一對一模型、多對一模型、多對多模型,在這以4個核心線程、3個使用者線程為例對三種模型進行說明。
對于一對一模型來說,一個使用者線程就唯一地對應一個核心線程(反過來不一定成立,一個核心線程不一定有對應的使用者線程)。這樣,如果CPU沒有采用超線程技術(如四核四線程的計算機),一個使用者線程就唯一地映射到一個實體CPU的線程,線程之間的并發是真正的并發。一對一模型使使用者線程具有與核心線程一樣的優點,一個線程因某種原因阻塞時其他線程的執行不受影響;此處,一對一模型也可以讓多線程程式在多處理器的系統上有更好的表現。
但一對一模型也有兩個缺點:1.許多作業系統限制了核心線程的數量,是以一對一模型會使使用者線程的數量受到限制;2.許多作業系統核心線程排程時,上下文切換的開銷較大,導緻使用者線程的執行效率下降。
圖 7:一對一模型
多對一模型将多個使用者線程映射到一個核心線程上,線程之間的切換由使用者态的代碼來進行,是以相對一對一模型,多對一模型的線程切換速度要快許多;此外,多對一模型對使用者線程的數量幾乎無限制。但多對一模型也有兩個缺點:1.如果其中一個使用者線程阻塞,那麼其它所有線程都将無法執行,因為此時核心線程也随之阻塞了;2.在多處理器系統上,處理器數量的增加對多對一模型的線程性能不會有明顯的增加,因為所有的使用者線程都映射到一個處理器上了。
圖 8:多對一模型
多對多模型結合了一對一模型和多對一模型的優點,将多個使用者線程映射到多個核心線程上。多對多模型的優點有:1.一個使用者線程的阻塞不會導緻所有線程的阻塞,因為此時還有别的核心線程被排程來執行;2.多對多模型對使用者線程的數量沒有限制;3.在多處理器的作業系統中,多對多模型的線程也能得到一定的性能提升,但提升的幅度不如一對一模型的高。
在現在流行的作業系統中,大都采用多對多的模型。
圖 9:多對多模型
一個應用程式可能是多線程的,也可能是多程序的,如何檢視呢?在Windows下我們隻須打開任務管理器就能檢視一個應用程式的程序和線程數。按“Ctrl+Alt+Del”或右鍵快捷工具欄打開任務管理器。
檢視程序數和線程數:
圖 10:檢視線程數和程序數
在“程序”頁籤下,我們可以看到一個應用程式包含的線程數。如果一個應用程式有多個程序,我們能看到每一個程序,如在上圖中,Google的chrome浏覽器就有多個程序。同時,如果打開了一個應用程式的多個執行個體也會有多個程序,如上圖中我打開了兩個cmd視窗,就有兩個cmd程序。如果看不到線程數這一列,可以在點選“檢視\選擇列”菜單,增加監聽的列。
檢視CPU和記憶體的使用率:
在性能頁籤中,我們可以檢視CPU和記憶體的使用率,根據CPU使用記錄的螢幕的個數還能看出邏輯處理核心的個數,如我的雙核四線程的計算機就有四個螢幕。
圖 11:檢視CPU和記憶體的使用率
當線程的數量小于處理器的數量時,線程的并發是真正的并發,不同的線程運作在不同的處理器上。但當線程的數量大于處理器的數量時,線程的并發會受到一些阻礙,此時并不是真正的并發,因為此時至少有一個處理器會運作多個線程。
在單個處理器運作多個線程時,并發是一種模拟出來的狀态。作業系統采用時間片輪轉的方式輪流執行每一個線程。現在,幾乎所有的現代作業系統采用的都是時間片輪轉的搶占式排程方式,如我們熟悉的Unix、Linux、Windows及Mac OS X等流行的作業系統。
我們知道線程是程式執行的最小機關,也是任務執行的最小機關。在早期隻有程序的作業系統中,程序有五種狀态,建立、就緒、運作、阻塞(等待)、退出。早期的程序相當于現在的隻有單個線程的程序,那麼現在的多線程也有五種狀态,現在的多線程的生命周期與早期程序的生命周期類似。
圖 12:早期程序的生命周期
程序在運作過程有三種狀态:就緒、運作、阻塞,建立和退出狀态描述的是程序的建立過程和退出過程。
建立:程序正在建立,還不能運作。作業系統在建立程序時要進行的工作包括配置設定和建立程序控制塊表項、建立資源表格并配置設定資源、加載程式并建立位址空間;
就緒:時間片已用完,此線程被強制暫停,等待下一個屬于他的時間片到來;
運作:此線程正在執行,正在占用時間片;
阻塞:也叫等待狀态,等待某一事件(如IO或另一個線程)執行完;
退出:程序已結束,是以也稱結束狀态,釋放作業系統配置設定的資源。
圖 13:線程的生命周期
建立:一個新的線程被建立,等待該線程被調用執行;
退出:一個線程完成任務或者其他終止條件發生,該線程終止進入退出狀态,退出狀态釋放該線程所配置設定的資源。