多進程、多線程、多核CPU——(I)

前言

在介紹多線程時,首先分析進程、以及多道程序設計模型。進程是操作系統中最重要的抽象概念之一,使得在即使只有一個CPU的機器上,也支持(僞)併發,即將一個單獨的CPU變成多個虛擬的CPU。

多道程序設計

在進程執行過程中常常會因爲資源請求或者IO被阻塞或中斷(有的請求或者中斷需要處理很長時間),此時CPU便空閒出來,衆所周知,CPU是計算機中非常寶貴的資源,爲提高其利用率,操作系統需通過進程切換,將CPU交給就緒隊列的某個進程使用;等上次被阻塞或者中端的進程再次滿足執行條件(一般是請求的資源得到滿足或者IO完成等)後,操作系統便通過調度算法將CPU再次交給該進程執行任務,其中操作系統的進程調度算法有多種(例如在交互式系統(windows)中的進程調度算法有時間片輪轉調度、優先級調度、多級隊列調度等,這些相信大家都很熟悉了,此處不再進一步闡述),具體選擇依賴操作系統。這時從用戶的角度感覺是多個進程在同時執行,這的易於操作系統通過進程調度將一個cpu變成多個虛擬的CPU,實現多個進程的僞併發。在《現在操作系統》中將這種進程間的切換稱之爲“多道程序設計”。

進程切換:由於每個進程的處理任務異樣性,其進程的輸入、輸出、處理過程、處理狀態的都不同,那在進程切換的過程中是否也應該考慮這些參數呢?答案是肯定的。一個正在執行的進程包括程序計數器、寄存器、變量的當前值等,而這些數據都是保存在CPU的寄存器中的,且這些寄存器只能是正在使用CPU的進程才能享用,所以在進程切換時,首先得保存上一個進程的這些數據(便於下次獲得CPU的使用權時從上次的中斷處開始繼續順序執行,而不是返回到進程開始,否則每次進程重新獲得CPU時所處理的任務都是上一次的重複,可能永遠也到不了進程的結束出,因爲一個進程幾乎不可能執行完所有任務後才釋放CPU),然後將本次獲得CPU的進程的這些數據裝入CPU的寄存器從上次斷點處繼續執行剩下的任務。操作系統爲了便於管理系統內部進程,爲每個進程創建了一張進程表項,如表1所示。

 

進程管理

存儲管理

文件管理

寄存器

正文段指針

根目錄

程序計數器

數據段指針

工作目錄

程序狀態字

堆棧指針

文件描述符

堆棧指針

 

用戶ID

進程狀態

 

組ID

優先級

 

 

調度參數

 

 

進程ID

 

 

父進程

 

 

進程組

 

 

信號

 

 

進程開始時間

 

 

使用CPU的時間

 

 

子進程的CPU時間

 

 

下次報警時間

 

 

多道程序設計的基本知識回顧差不多了,下慢我們來探討下多道程序設計模型。

從上面的分析可以得知,多道程序設計可以提高cpu的利用率。但嚴格來講,如果進程的計算平均時間是進程在內存中停留時間的20%,且內存中同時有5個進程,則CPU將一直處於滿負荷狀態,然而在模擬在實際中過於樂觀,因爲其假設這5個進程不會同時等待IO。

下面我們從概率的角度分析CPU的利用率,假設進程等待IO操作的時間與其停留內存的時間比例爲p,當內存有n個進程時,則n個進程同時等待IO的概率爲pn。則CPU的利用率爲:

CPU的利用率 = 1 - pn


圖1以n爲變量的函數表示了CPU的利用率,n爲多道程序設計的道數。

從圖中可以看出當進程花80%進行IO時(IO密集型),需要大約10個進程併發才能使CPU得到充分利用;而當進程只花20%的時間進行IO時(稱CPU密集型),孩子需要2個進程就可以使CPU的浪費率低於10%。在實際的應用中,不管一個等待用戶從終端輸入的交互式進程還是做大量讀寫磁盤的服務器進程80%甚至更多的IO時間是普遍的,所以通過多道程序設計模式可以提高IO密集型進程的CPU利用率,從而間接提高了整個系統的吞吐量;而對於CPU密集型進程,其併發度與CPU利用率不一定是成真比例。這套標準體系對與多線程同樣適用,我們稍後進一步分析。

多道程序設計就回顧到這裏,可見其核心就是通過進程調度提高CPU的利用率,將一個CPU虛擬成多個,實現多個進程的併發執行,至於進程如何創建、銷燬、以及狀態和狀態轉換、進程的層次結構以及進程的實現,此處就不闡述了,如果答不上或者不能隨手捻來的話,還是回去看看《現代操作系統》這本書吧。

 

下面我們就一起談談線程的相關知識以及與進程的關係。當你讀到這裏,有的同鞋肯定會問,既然多道程序設計可以提高CPU的利用率,並實現多個進程的併發執行。如果你在提這個問題,表示你在思考,如果你還沒有意識到這個問題,建議你停下結合之前學習的知識先想一想,看看是否自己能給出一個答案?

線程

我們先看看維基百科對線程的定義:線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是行程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以並行多個線程,每條線程並行執行不同的任務。在Unix System V及SunOS中也被稱爲輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱爲線程。此外,從資源分配的角度看,進程是資源所有資源分配的基本單位,線程則是CPU調度的基本單位,即使在單線程進程中也是如此。

 

引入線程的原因:

1)  一個應用程序中同時存在多個任務,其中的部分活動會隨時間的推移而阻塞,而另外一部分則不會,例如,一個文字處理軟件,前臺部分需要從終端設備獲得輸入或者將處理完的部分輸出,而後臺線程則可以實現對文字的處理。故對於CPU密集型進程,該用多線程其性能不一定能得到很大提高,但對於IO密集型進程,其性能可得到很大提高。

2)  線程比進程更輕量級,創建和撤銷的代價小,在許多系統中,創建一個線程比一個進程要快10~100倍不等。

3)  在多核CPU中,真正的並行有了可能。即在多線程設計中一部分可用來處理前臺任務,一部分可用來處理後臺任務,實現真正意義上的並行。

4)    線程間的切換代價要比進程切換的代價小。


引入多線程的原因:

1)某個操作可能會陷入長時間等待,等待的線程會進入睡眠狀態,無法繼續執行。多線程執行可以有效利用等待時間。如等待網絡響應可能需要幾秒的時間。

2)某個操作(常常是計算)會消耗大量的時間,如果只有一個線程,程序和用戶之間的交互會中斷。多線程可以讓一個線程負責交付,另一個線程負責計算。

3)多CPU或者多核計算機,本身具備同時執行多個線程的能力,故單線程無法完全發揮計算機的計算能力。

4)相對於多進程應用,多線程在數據共享方面效率要高很多。

5)程序邏輯本身就要求併發操作。


現在我們通過考察一個例子,就可以更清楚看出多線程的有益之處了。

假設用戶正在編輯一本書。對於編輯這來說,最容易的辦法是把正本書作爲一個文件,便於編輯;而對於計算機來說把每個章節作爲一個文件處理起來更快,但對於編輯者來說修改就太麻煩了,因爲有的修改不止設計一個章節而是整本書,例如在整本書中替換某個詞或字等等,如果整本書作爲一個文件,正樣處理就方便多了。否則,就得對每個章節所在文件進行處理。

現在如果用在一個1000頁的文檔中刪除第一頁的某一行的某個詞,爲保證格式的正確性,字處理軟件需要對文檔進行格式處理。但此時用戶需要立刻跳到地800也進行另外一處修改,於是字處理軟件被強制對整個書的前800頁進程格式處理,因爲在排列該頁前面的所有頁面之前,字處理軟件並不知道第800頁的第一行應該在哪裏。而在第800也的頁面可以顯示在屏幕之前,計算機可能要拖延想當長一段時間進行處理,從而令用戶不甚滿意。

此時,多線程便可以有用武之地了。假設字處理軟件編寫成含有兩個線程的程序。一個線程處理用戶的交互,另一個用來在後臺進行格式處理。一旦第一頁發生的修改,交互線程就立即同時後臺格式處理線程重現整理整本書的格式。同時,交互式線程繼續監控用戶的鼠標、鍵盤,並響應諸如第一頁之類的簡單命令,此刻,後臺線程正在進行瘋狂的運算,如果運氣好的話,格式整理可能在用戶請求查看第800頁之前完成,這樣用戶就感覺不到延遲了。

同理,爲保證用戶的編輯工作得到及時保存,可以在添加一個現場週期性對文件進現場可以處理磁盤備份,而不必干擾其他兩個線程。擁有三個線程的情形如圖2所示。

圖2 三個線程的字處理軟件

試想,如果是單線程的話,那麼在進行磁盤備份的時,來自鍵盤或者鼠標的命令就會被忽略,直至備份完成。有的同鞋會說,可以引入中斷機制來中止備份操作,相應鼠標和鍵盤的命令,但其複雜性可想而知。如果引入三個線程,其設計就簡單多了,一個線程用於與用戶交互,第二線程在得到地一個線程的通知後在後臺進行文檔的格式化處理,第三個線程則週期性將ARM內的內容被封到磁盤。

此處,很顯然,這裏用三個不同的進程是不能工作的,因爲三個線程都需要在同一個文件上進行操作,通過三個線程,由於一個進程內的所有線程共享公共內存,於是便可以在同一文件上進行處理。同理,其他的交互式程序也可以採用同樣的設計方法。

看完這個例子,部分對多道程序設計比較忠心的同學可能會問,上述三個線程能協同完成工作,主要的便利之處在於其共享了進程中的公共內存空間。同樣,也可以採用進程通信的方式來協同完成工作?答案確實如此,但仔細思考幾個問題:1、進程通信與線程通信的代價孰高孰第?2、進程切換和線程切換的代價?3、如何保證三個進程所處理內容的一致性?而多線程方案中由於都是對同一文檔內容進行處理,其一致性的保證則簡單很多。如果能準確解答上述幾個答案,爲什麼不選擇多道程序設計方案來完成其上述工作的原因就不攻自破了吧。

線程的內容我們回顧差不多了,現在我們來一起看看進程與線程的關係和區別吧。

進程與線程:

進程是操作系統的管理單位,而線程則是進程的管理單位;一個線程至少包含一個執行線程。不管是在單線程還是多線程中,每個線程都有一個程序計數器(記錄要執行的下一條指令),一組寄存器(保存當前線程的工作變量),堆棧(記錄執行歷史,其中每一幀保存了一個已經調用但爲返回的過程)。雖然線程寄生在進程中,但與他的進程是不同的概念,並且可以分別處理:進程是系統分配資源的基本單位,線程時調度CPU的基本單位。

多線程是對多進程的模擬。前者,多個線程共享同一個地址空間和其他資源,後者共享物理內存、磁盤、IO等其他資源,故線程被稱爲“輕量級進程”。多線程在但CPU系統中運行時,線程輪流運行,猶如多道程序設計,製造線程並行運行的假象。在一個有三個CPU密集型的進程中,實際上每個線程在一個CPU上得到的真實CPU速度的三分之一。不過隨着技術的發展,目前主流的CPU都已經直接硬件支持多線程,並允許線程在幾個納秒級內完成切換。後續會對多進程、多線程、以及多核之間的關係進行總結。

線程間不像進程之間那樣存在很大的獨立性,一個進程的多個線程共享進程內部的很多資源,線程間可以互寫對方的堆棧,而不同的進程則無法對其他進程的地址空間進行寫操作。因此,在實現多線程編程中,應設計合理的同步通信機制,避免數據衝突的現象發生。圖3給出了進程、線程的內容,其中進程的內容是該進程的所有線程共享的。

每個進程中的內容

每個線程中的內容

地址空間

程序計數器

全局變量

寄存器

打開文件

堆棧

子進程

狀態

即將發生的報警

 

信號與信號處理程序

 

賬戶信號

 

同步、互斥信號量

 

 本節就先寫到這裏,這要闡述了多進程、多線程。下一節將闡述兩種線程實現方法以及與多核中的多進程與多線程的聯繫。

 

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