1. 程式人生 > >淺談程序、執行緒和協程三者之間的區別和聯絡

淺談程序、執行緒和協程三者之間的區別和聯絡

一、程序、執行緒、協程

1,程序

經典定義:一個執行中程式的例項。系統中的每個程式都執行在某個程序的上下文中。(-摘自 CSAPP)

程序是系統資源分配的最小單位

 

2,執行緒(thread)

執行緒就是執行在程序上下文中的邏輯流。

執行緒是作業系統能夠進行運算排程的最小單位。

 

3,協程

相對子例程而言,協程更為一般和靈活,但在實踐中使用沒有子例程那樣廣泛。

根據維基百科對子例程的描述:是一個大型程式中的某部分程式碼,由一個或多個語句塊組成。它負責完成某項特定任務,而且相較於其他程式碼,具備相對的獨立性。我可以將子例程理解為一個函式。

 

4,區別和聯絡

首先,程序提供給應用程式的關鍵抽象為:

  • 一個獨立的邏輯控制流:它提供一個假象,好像我們的程式獨佔地使用處理器。
  • 一個私有的地址空間,它提供一個假象,好像我們的程式獨佔地使用記憶體系統。

從以上描述我可以看出,一個程序是一個獨立進行的任務,它佔用的系統資源有:地址空間,全域性變數,檔案描述符,硬體資源等。

 

程序出現的目的,是為了更好的利用CPU資源。例如:

假設有兩個任務A和B,當A遇到IO操作,CPU默默的等待任務A讀取完操作再去執行任務B,這樣無疑是對CPU資源的極大的浪費。若在任務A讀取資料時,讓任務B執行,當任務A讀取完資料後,再切換

到任務A執行,這樣就可以更好地利用CPU資源。這裡的切換涉及到狀態的儲存,狀態的恢復,需要有一個東西去記錄任務A和任務B分別需要什麼資源,怎樣去識別任務A和任務B,這時程序就出現了。

因此,通過程序來分配系統資源,標識任務。

如何分配CPU去執行程序稱之為排程,程序狀態的記錄,恢復,上下文切換(簡稱切換)。

 

其次,若上面提及的任務A是一個文字程式,需要接受鍵盤輸入,將內容顯示在螢幕上,還需要儲存資訊到硬碟中。

若只有一個程序,會造成同一時間只能幹一樣事的尷尬(當儲存時,就不能通過鍵盤輸入內容)。若有多個程序,每個程序負責一個任務,程序A負責接收鍵盤輸入的任務,程序B負責將內容顯示在螢幕上的任務,程序C負責儲存內容到硬碟中的任務。這裡程序A,B,C間的協作涉及到了程序通訊問題,而且有共同都需要擁有的東西-------文字內容,不停的切換造成效能上的損失。若有一種機制,可以使任務A,B,C共享資源,這樣上下文切換所需要儲存和恢復的內容就少了,同時又可以減少通訊所帶來的效能損耗,那就好了。這時執行緒出現了。 因此,執行緒共享程序的大部分資源,並參與CPU的排程。
    假設當涉及到大規模的併發請求連線時,例如有一萬個人同時連線我的伺服器,但系統資源有限,如果以執行緒作為處理單元,調內部系統資源的話大部分執行緒都處於等待狀態,但用了協程就可以實現執行緒自己排程,不陷入核心級別的上下文切換。這時協程出現了。 因此,協程通過線上程中實現排程,避免了陷入核心級別的上下文切換造成的效能損失,進而突破了執行緒在IO上的效能瓶頸。   為什麼協程不需要經過核心級別的上下文切換,我是這樣認為的: 程序和執行緒都是作業系統自帶的,協程是有些程式原生支援的,例如go,lua, 有些是後期版本才有的,比如python2.5 C#等。    

二、小結

之前做遊戲伺服器時就對這塊概念不是很清晰,現在做流媒體服務時又碰到了這樣的困惑,因此專門看書思考整理了相關的知識點,網上也參考了許多例子。如果有錯誤還望及時指正。 在此引用一位從事伺服器開發的前輩說的話:
核心只有一個, 執行緒是作業系統排程,協程是使用者態排程。
協程不必須是語言整合,例如C語言可以用setjmp/longjmp實現,也可以自己通過改變esp指標換棧實現協程。 協程本身跟高吞吐沒任何關係,基於io多路複用+回撥就可以實現高併發和高吞吐。 引入協程是為了將回調邏輯變成線性同步邏輯。
 

參考資料

維基百科_程序

維基百科_執行緒

維基百科_協程

簡書_程序,執行緒,協程與並行,併發

程序、執行緒、輕量級程序、協程與 go 的 goroutine【轉載+整理】

部落格園_程序和執行緒、協程的區別