天天看點

淺談我對協程的了解 我心中的協程

最近在研究網絡服務架構方面的東西,發現了一個神奇的東西-協程。

一句話說明什麼是線程:協程是一種使用者态的輕量級線程。

一句話并不能完全概括協程的全部,但是起碼能讓我們對協程這個概念有一個基本的印象。

從硬體發展來看,從最初的單核單cpu,到單核多cpu,多核多cpu,似乎已經到了極限了,但是單核cpu性能卻還在不斷提升。server端也在不斷的發展變化。如果将程式分為io密集型應用和cpu密集型應用,二者的server的發展如下:

io密集型應用: 多程序->多線程->事件驅動->協程

cpu密集型應用:多程序-->多線程

如果說多程序對于多cpu,多線程對應多核cpu,那麼事件驅動和協程則是在充分挖掘不斷提高性能的單核cpu的潛力。

以下的讨論如無特别說明,不考慮cpu密集型應用。

無論是線程還是程序,使用的都是同步進制,當發生阻塞時,性能會大幅度降低,無法充分利用cpu潛力,浪費硬體投資,更重要造成軟體子產品的鐵闆化,緊耦合,無法切割,不利于日後擴充和變化。不管是程序還是線程,每次阻塞、切換都需要陷入系統調用(system call),先讓cpu跑作業系統的排程程式,然後再由排程程式決定該跑哪一個程序(線程)。多個線程之間在一些通路互斥的代碼時還需要加上鎖,這也是導緻多線程程式設計難的原因之一。

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

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

之前說道,協程是一種使用者級的輕量級線程。協程擁有自己的寄存器上下文和棧。協程排程切換時,将寄存器上下文和棧儲存到其他地方,在切回來的時候,恢複先前儲存的寄存器上下文和棧。是以:

協程能保留上一次調用時的狀态(即所有局部狀态的一個特定組合),每次過程重入時,就相當于進入上一次調用的狀态,換種說法:進入上一次離開時所處邏輯流的位置。

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

不管是程序還是線程,每次阻塞、切換都需要陷入系統調用(system call),先讓cpu跑作業系統的排程程式,然後再由排程程式決定該跑哪一個程序(線程)。

而且由于搶占式排程執行順序無法确定的特點,使用線程時需要非常小心地處理同步問題,而協程完全不存在這個問題(事件驅動和異步程式也有同樣的優點)。

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

以nginx為代表的事件驅動的異步server正在橫掃天下,那麼事件驅動模型會是server端模型的終點嗎?

我們可以深入了解下,事件驅動程式設計的模型。

事件驅動程式設計的架構是預先設計一個事件循環,這個事件循環程式不斷地檢查目前要處理的資訊,根據要處理的資訊運作一個觸發函數。其中這個外部資訊可能來自一個目錄夾中的檔案,可能來自鍵盤或滑鼠的動作,或者是一個時間事件。這個觸發函數,可以是系統預設的也可以是使用者注冊的回調函數。

事件驅動程式設計着重于彈性以及異步化上面。許多gui架構(如windows的mfc,android的gui架構),zookeeper的watcher等都使用了事件驅動機制。未來還會有其他的基于事件驅動的作品出現。

基于事件驅動的程式設計是單線程思維,其特點是異步+回調。

協程也是單線程,但是它能讓原來要使用異步+回調方式寫的非人類代碼,可以用看似同步的方式寫出來。它是實作推拉互動的所謂非搶占式協作的關鍵。

協程的好處:

跨平台

跨體系架構

無需線程上下文切換的開銷

無需原子操作鎖定及同步的開銷

友善切換控制流,簡化程式設計模型

高并發+高擴充性+低成本:一個cpu支援上萬的協程都不是問題。是以很适合用于高并發處理。

缺點:

無法利用多核資源:協程的本質是個單線程,它不能同時将 單個cpu 的多個核用上,協程需要和程序配合才能運作在多cpu上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。

進行阻塞(blocking)操作(如io時)會阻塞掉整個程式:這一點和事件驅動一樣,可以使用異步io操作來解決

繼續閱讀