1. 程式人生 > >淺談我對協程的理解

淺談我對協程的理解

一句話說明什麼是執行緒:協程是一種使用者態的輕量級執行緒

一句話並不能完全概括協程的全部,但是起碼能讓我們對協程這個概念有一個基本的印象。
從硬體發展來看,從最初的單核單CPU,到單核多CPU,多核多CPU,似乎已經到了極限了,但是單核CPU效能卻還在不斷提升。server端也在不斷的發展變化。如果將程式分為IO密集型應用和CPU密集型應用,二者的server的發展如下:
IO密集型應用: 多程序->多執行緒->事件驅動->協程
CPU密集型應用:多程序-->多執行緒

如果說多程序對於多CPU,多執行緒對應多核CPU,那麼事件驅動和協程則是在充分挖掘不斷提高效能的單核CPU的潛力。
以下的討論如無特別說明,不考慮cpu密集型應用。

非同步 vs 同步

無論是執行緒還是程序,使用的都是同步進位制,當發生阻塞時,效能會大幅度降低,無法充分利用CPU潛力,浪費硬體投資,更重要造成軟體模組的鐵板化,緊耦合,無法切割,不利於日後擴充套件和變化。不管是程序還是執行緒,每次阻塞、切換都需要陷入系統呼叫(system call),先讓CPU跑作業系統的排程程式,然後再由排程程式決定該跑哪一個程序(執行緒)。多個執行緒之間在一些訪問互斥的程式碼時還需要加上鎖,這也是導致多執行緒程式設計難的原因之一。

現下流行的非同步server都是基於事件驅動的(如nginx)。事件驅動簡化了程式設計模型,很好地解決了多執行緒難於程式設計,難於除錯的問題。非同步事件驅動模型中,把會導致阻塞的操作轉化為一個非同步操作,主執行緒負責發起這個非同步操作,並處理這個非同步操作的結果。由於所有阻塞的操作都轉化為非同步操作,理論上主執行緒的大部分時間都是在處理實際的計算任務,少了多執行緒的排程時間,所以這種模型的效能通常會比較好。

總的說來,當單核cpu效能提升,cpu不在成為效能瓶頸時,採用非同步server能夠簡化程式設計模型,也能提高IO密集型應用的效能。

協程 vs 執行緒

之前說道,協程是一種使用者級的輕量級執行緒。協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧。因此:

協程能保留上一次呼叫時的狀態(即所有區域性狀態的一個特定組合),每次過程重入時,就相當於進入上一次呼叫的狀態,換種說法:進入上一次離開時所處邏輯流的位置。

在併發程式設計中,協程與執行緒類似,每個協程表示一個執行單元,有自己的本地資料,與其它協程共享全域性資料和其它資源。目前主流語言基本上都選擇了多執行緒作為併發設施,與執行緒相關的概念是搶佔式多工(Preemptive multitasking)

,而與協程相關的是協作式多工

不管是程序還是執行緒,每次阻塞、切換都需要陷入系統呼叫(system call),先讓CPU跑作業系統的排程程式,然後再由排程程式決定該跑哪一個程序(執行緒)。
而且由於搶佔式排程執行順序無法確定的特點,使用執行緒時需要非常小心地處理同步問題,而協程完全不存在這個問題(事件驅動和非同步程式也有同樣的優點)。

我們在自己在程序裡面完成邏輯流排程,碰著i\o我就用非阻塞式的。那麼我們即可以利用到非同步優勢,又可以避免反覆系統呼叫,還有程序切換造成的開銷,分分鐘給你上幾千個邏輯流不費力。這就是協程。

協程 vs 事件驅動

以nginx為代表的事件驅動的非同步server正在橫掃天下,那麼事件驅動模型會是server端模型的終點嗎?
我們可以深入瞭解下,事件驅動程式設計的模型。
事件驅動程式設計的架構是預先設計一個事件迴圈,這個事件迴圈程式不斷地檢查目前要處理的資訊,根據要處理的資訊執行一個觸發函式。其中這個外部資訊可能來自一個目錄夾中的檔案,可能來自鍵盤或滑鼠的動作,或者是一個時間事件。這個觸發函式,可以是系統預設的也可以是使用者註冊的回撥函式。

事件驅動程式設計著重於彈性以及非同步化上面。許多GUI框架(如windows的MFC,Android的GUI框架),Zookeeper的Watcher等都使用了事件驅動機制。未來還會有其他的基於事件驅動的作品出現。

基於事件驅動的程式設計是單執行緒思維,其特點是非同步+回撥。
協程也是單執行緒,但是它能讓原來要使用非同步+回撥方式寫的非人類程式碼,可以用看似同步的方式寫出來。它是實現推拉互動的所謂非搶佔式協作的關鍵。

總結

協程的好處:

  • 跨平臺
  • 跨體系架構
  • 無需執行緒上下文切換的開銷
  • 無需原子操作鎖定及同步的開銷
  • 方便切換控制流,簡化程式設計模型
  • 高併發+高擴充套件性+低成本:一個CPU支援上萬的協程都不是問題。所以很適合用於高併發處理。

缺點:

  • 無法利用多核資源:協程的本質是個單執行緒,它不能同時將 單個CPU 的多個核用上,協程需要和程序配合才能執行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  • 進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程式:這一點和事件驅動一樣,可以使用非同步IO操作來解決