程式與執行緒--原理

WEB發表於2014-03-05

        所謂白話即是將事物的原理用通俗易懂的語言表達出來,接下來我們就說一說我們平時用到的程式與執行緒在作業系統中是如何被管理以及排程的。

        其實作業系統本質上的意義就是如何讓我們更方便的來使用這些如 cpu、記憶體、網路卡 等物理設施,給我們的生活帶來便利或更優質的生活享受。如我們開啟電腦後,啟動作業系統,安裝應用就可以線上看電視或者打遊戲。或者對於我們技術人員來講,在我們的開發過程中,假如我們要讀取硬碟中的資料,我們直接呼叫read系統呼叫就可以,我們無需去關心磁頭的移動與柱面扇區如何移動才能讀出資料。或者我們分配記憶體,我們直接呼叫malloc系統呼叫就可以分配記憶體,我們也無需關心記憶體條到底還有多少空閒。

        所以作業系統就是將我們從那些瑣碎的重複的勞動中解放出來,方便我們的生活與工作。 那麼我們開始今天的話題,先說一下程式,然後再說一執行緒,最後再總結一下程式與執行緒的區別與應用方面的考慮。

        什麼是程式?

        我們的計算機要為我們的生活服務,我們一邊寫著部落格,同時還得聊著QQ,還得幫我收取郵件,我們希望計算機同時能幫我們做到這些事情,那麼就有了程式。程式就是在作業系統中做某件事情的一段程式的例項,現在我的電腦裡就執行著瀏覽器程式、QQ客戶端程式、郵件程式還有其他作業系統的一些基礎程式,所以計算機能在我寫部落格的時候收取郵件還能傳送QQ訊息,當然對於cpu來講,某一個時刻只會執行一個程式,但是我們的作業系統有任務排程程式,讓cpu根據排程來在這些程式之間不斷的切換。在一秒鐘之內,作業系統就會在不同的程式中切換很多次,這也是對於我們人來講,感覺就像是計算機在同時做這幾件事情,但對於cpu來講他是在不同程式之間的執行來回切換。

      程式在作業系統中是如何管理的?

      我們通過上面知道了,作業系統中執行著各種不同任務的程式,有收發郵件的、有聊天的、有瀏覽器的,但是作業系統是如何讓這些程式不斷的來回切換並且是如何切換的呢。

      我們知道其實計算機的就是在不斷的進行計算,對資料進行處理。cpu傳送指令從記憶體中讀取程式指令然後通過匯流排放入不同的暫存器,然後再從暫存器中讀入cpu進行運算然後再放入暫存器或寫入記憶體。

      其實一切的程式都是如此執行,當在作業系統中執行一個程式就是啟動一個程式,在作業系統中執行一個程式的時候,如果是第一次執行,作業系統就會為此程式分配記憶體空間,這些空間包括不可更改的指令空間(文字段),就是儲存要執行的程式的指令的;資料段,變數的值都存在這裡;還有棧(堆疊)空間,這裡存放在呼叫函式時區域性變數在計算過程中的變化以及結果,隨著函式呼叫或結束,這裡的空間也在增加或縮小;還有堆空間,程式中動態分配的記憶體空間將分配在這裡。而後作業系統將指令空間的記憶體地址放入暫存器,由cpu開始讀取執行,沿著程式碼段執行,在執行過程中為了儲存結果或執行進度將變數的值或函式的呼叫過程放入相應的空間以及指令的執行進度放入相應的暫存器。

       在作業系統中系統維護著一張程式表,這裡儲存著系統中執行的所有程式,每一項程式都佔用一個程式表項。這個表項資料結構中儲存著程式的很多重要資訊,如在系統排程讓cpu執行下一的程式時,把當前程式執行時的映象全部儲存下來,為了當再次執行此程式時恢復當時執行的場景。比如 程式計算器(程式執行到哪) 堆疊指標(執行過程中的變相值) 各暫存器中的值 檔案的開啟狀態 等一切執行時的資訊,還有程式本身的一些資訊 比如執行此程式的帳號以及優先順序之類的屬性資訊。

       所以,作業系統是根據排程程式來決定執行哪個程式,當要執行某個程式時,會把當時正在執行的程式時的一起用於恢復當時場景的資料都儲存在系統維護的程式表中對應的程式資訊儲存的資料結構中,當排程程式再次執行此程式時,將儲存的這些資訊改放入暫存器的放入對應的起存期,恢復到堆疊的恢復到堆疊,繼續執行上一次執行指令的下一次指令。

      什麼是執行緒?

     之所以會有執行緒,是因為在程式中執行多種活動時,當進行某一項活動時比如讀取磁碟資料,此時程式就處於阻塞狀態,整個事情的進展就處於暫時的停止狀態,如果我們將程式中的多種工作分派給不同的執行緒去處理,比如一個執行緒在向記憶體中寫入資料,一個執行緒在分析資料,這樣就不會在讀取資料的時候無法分析資料了。這就是為什麼會有執行緒的概念。

     其次執行緒可以共享程式中的資源比如開啟的檔案、全域性變數等公共資源,另外執行緒比程式更輕量級,建立和銷燬比程式要快10-100倍。這兩個也是執行緒之所以存在的原因。

     執行緒在程式的記憶體空間中有自己的記憶體空間用來臨時儲存自己的堆疊或暫存器內容或程式計數器,用來恢復繼續執行,程式中不同的執行緒不像不同的程式之間存在著很大的獨立性,所有執行緒都有完全一樣的地址空間,這意味著執行緒之間共享全域性變數。由於各個執行緒都可以訪問程式地址空間中的任意記憶體地址,所以一個執行緒可以修改讀取甚至刪除另一執行緒的堆疊,執行緒之間是沒有保護的。因為不同的執行緒肯定來自同一程式也就是同一使用者,他們之間不會有敵意,他們之間還可以共享開啟的檔案集、子程式、以及相關訊號。而不同的執行緒之間的通訊就複雜的多,同一程式中的多個執行緒是相互信任的,而不同的程式之間是不信任的。

其實執行緒就是程式中的又一執行單位,程式中不同的執行緒只是執行的進度不同,其他都是可以共享的。

程式空間地址儲存的內容 執行緒空間地址中存在的內容
地址空間 程式計數器
全域性變數 暫存器
開啟的檔案集 堆疊
子程式 狀態
賬戶資訊  
優先順序  
訊號  

     那麼什麼時候該使用執行緒?因為我們知道執行緒是在程式中的執行時不斷交替執行的單位,當某一執行緒遇到io阻塞,這個時候讓當前執行緒等待匯流排上的資料,儲存當前執行緒的現場(堆疊和暫存器),執行另一執行緒。所以當某項工作大多時進行的都是cpu運算,那麼多執行緒顯然毫無意義,而且還會增加執行緒之間切換儲存執行緒的額外工作,所以cpu密集型的工作不適合用執行緒。但是當工作中有運算和iO處理工作時,顯然將這些工作分配給多執行緒是可以不浪費cpu寶貴的運算時間,讓cpu忙起來提高工作效率的好辦法。

    那麼作業系統如何管理執行緒的呢?我們應該如何利用這種機制呢?

    第一種是作業系統不知道程式中執行著多執行緒,我們在使用者空間呼叫庫函式在程式中執行和排程多個執行緒,通過維護一張執行緒表來管理這些執行緒,作業系統拿這個程式就當是普通的程式。

     這種方式的優點就是作業系統計算不支援多執行緒機制,我們也可以照樣使用多執行緒來高效的完成我們的工作。我們可以在程式中通過自己的演算法來排程執行緒的執行,而且在程式中建立或銷燬執行緒比在作業系統核心空間中要節約資源,無需像在核心中發生大量的上下文切換以及現場儲存工作。

     這種方式的缺點就是由於作業系統並不知道程式中的多執行緒,所以當程式中的某一個執行緒發生系統呼叫陷入系統核心中時,作業系統會將這個程式阻塞,執行其他程式,它不知道也不會管該程式中是否有其他執行緒在執行著。還有就是在使用者空間中執行多執行緒,當某一個多執行緒一直佔用cpu時,在當前程式的執行時刻,由於使用者空間中沒有時鐘機制,所以其他執行緒只能等待,無能為力。

     第二種就是作業系統支援多執行緒,那麼程式中的執行緒就交由作業系統來管理,當程式中某一執行緒發生阻塞時,作業系統會選擇此程式中的另一執行緒繼續執行,而且當某一執行緒佔用cpu時間過長時,作業系統也會根據時鐘阻塞執行緒,這也算是相對於使用者空間來說的優點。但是畢竟是在核心中由作業系統來管理執行緒,無論執行緒的建立還是管理消耗的資源都比在使用者空間大,這也是核心管理多執行緒的缺點。

     前面兩種模式都各有優缺點,如果能將其結合到一起,各取其優點,應該會更好。

     一種做法是使用核心級執行緒,然後將使用者級執行緒與核心級執行緒多路複用起來。這樣,使用者就可以決定有多少個使用者級執行緒和多少核心級執行緒多路複用,這樣會更加靈活。

      

相關文章