我看書有個特點,不寫筆記,就感覺好像沒有看過書,印象不深刻(雖然寫了也未必深刻),所以我看書會比較慢,筆記會很多。這里總結一下並發。
最近學習《深入理解計算機系統》,最后一章中講到了並發。之前一直以為並發是為了提高性能,書中將並發理解為邏輯控制流在時間上的重疊。簡單的說,就是讓機器
能夠同時處理多個事情,充分利用機器的能力。特別是現在多核機器的普遍,並發可能越來越重要。
現代操作系統提供了三種基本的方法用於構造並發:進程,I/O多路復用,線程。我們公司在工作中用的最多的其實是第二種。你也許知道,他是無法利用CPU的多核的。
不過還好,我們的服務器上一般會跑多個程序(系統划分為多個程序模塊)。
某些語言也從語言層面提供了一種並發的機制:協程,比如lua和python,go好像也提供了這種機制。我比較熟悉的是python的協程。
1、進程
收到客戶端的請求后,就申請一個新的進程來處理這個請求。這是利用進程並發的基本模式。
“進程有一個清晰的模型:共享文件表,但不共享用戶地址空間。”——我實際嘗試了一下,准確的說,應該是子進程會繼承他生成時父進程打開的文件描述符。當子進
程開始運行后,父進程或者子進程在打開的文件描述符將不會被他們共享。
被共享的文件描述符是引用計數,所以一般情況下,fork返回后,當父進程和子進程分別執行的時候,他們一般都要各自把不需要的文件描述符關閉掉。
linux2.4.22提供了另一種新進程創建函數:clone()系統調用。它允許調用者控制那部分由父子進程共享。
優點:獨立的地址空間,這樣各個進程隔絕,一個進程不會一不小心進入另外一個進程,減少了程序的復雜性,不易出錯。
缺點:1)進程間共享信息變得困難,需要通過進程間通信(IPC)來解決。2)多個進程要在cpu上進行上下文切換,可能會降低運行速度(如果為了利用多核,進程數目和cpu核數一致則另當別論)。另外IPC也會有開銷。
2、I/O多路復用
這是我們用的比較多的一種方式。之前寫過這方面的一篇文章:表驅動法。
書中叫做並發事件驅動程序,在事件驅動程序中,邏輯流程一般會被模型化為狀態機,當收到一個邏輯流開始請求后,我們就申請一個新的狀態機來處理這個請求以及后續的相關請求(狀態機我們一般情況下叫做通道)。
一個邏輯流的完成往往需要很多的事件,對於后續的事件,我們一般會首先找到時那個狀態機在處理它,然后根據事件類型,狀態機狀態來選擇相應的處理函數進行處理,以此推動狀態機的前進。
優點:1)一個進程可以處理多個邏輯流程;2)它比基於進程的設計給了程序員更多的控制權;3)每個邏輯流都能夠訪問進程的全部的地址空間,使得流之間共享數據變得更加簡單;4)因為沒有進程的上下文切換,可能會更加高效。
缺點:1)增加了代碼的復雜度。事件驅動機制使得對流程的處理被打的七零八散,整個程序只見事件,不見流程——流程隱藏在事件處理的轉移和銜接中,降低可讀性的同時,也增加了實現的復雜性。2)無法有效的利用多核處理器。
3、線程
線程的調度和進程的調度一樣,都是系統進行調度的,但是線程的切換要比進程快很多。同時,線程之間是對等的,沒有父子概念:“主線程和其他線程的區別僅在於它
是第一個運行的線程。”“每個線程都有獨立的線程上下文,包括線程ID,棧,棧指針,程序計數器,條件碼和通用目的寄存器值。每個線程都和其他線程共享進程上下文的其他部分:代碼段,數據段,BSS段,堆以及所有共享庫代碼和數據區域。線程通用共享打開文件的集合”
我在開發過程中發現很多人使用線程會遇到兩個問題:
1)線程沒有真正釋放。線程釋放有兩種方式,要么通過pthread_join顯示回收,但是這回阻塞主線程。要么分離每個線程,這樣,線程結束的時候會自動釋放。
2)線程安全。這是線程最為人詬病的地方,他會明顯的增加程序的復雜性。具體的還是看這篇文章:http://blog.csdn.net/lanphaday/article/details/7218611
優點:1)線程的切換比進程要快,而且是操作系統支持的。2)相對進程,更容易在線程間共享信息。3)也能夠利用多核的優勢。
缺點:1)線程會增加程序的復雜度,特別是線程安全問題,需要加倍小心。2)增加線程的同時性能可能會出現下降,因為線程的切換也會有開銷。
4、協程
這個是書上沒有,我最近接觸的。他也可以提供並發。目前所討論的協程,一般是編程語言提供支持的。目前我所知提供協程支持的語言包括python,lua,go,據說
scala和rust也支持。
協程不同於線程的地方在於協程不是操作系統進行切換,而是由程序員編碼進行切換的,也就是說切換是由程序員控制的,這樣就沒有了線程所謂的安全問題。
所有的協程都共享整個進程的上下文,這樣協程間的交換也非常方便。
相對於第二種方案(I/O多路復用),使得使用協程寫的程序將更加的直觀,而不是將一個完整的流程拆分成多個管理的事件處理。
協程的缺點可能是無法利用多核優勢,不過,這個可以通過協程+進程的方式來解決。
協程可以用來處理並發來提高性能,也可以用來實現狀態機來簡化編程。我用的更多的是第二個。去年年底接觸python,了解到了python的協程概念,后來通過pycon china2011接觸到處理yield,greenlet也是一個協程方案,而且在我看來是更可用的一個方案,特別是用來處理狀態機。
目前這一塊已經基本完成,后面抽時間總結一下。
總結一下:
1)多進程能夠利用多核優勢,但是進程間通信比較麻煩,另外,進程數目的增加會使性能下降,進程切換的成本較高。程序流程復雜度相對I/O多路復用要低。
2)I/O多路復用是在一個進程內部處理多個邏輯流程,不用進行進程切換,性能較高,另外流程間共享信息簡單。但是無法利用多核優勢,另外,程序流程被事件處理切割成一個個小塊,程序比較復雜,難於理解。
3)線程運行在一個進程內部,由操作系統調度,切換成本較低,另外,他們共享進程的虛擬地址空間,線程間共享信息簡單。但是線程安全問題導致線程學習曲線陡峭,而且易出錯。
4)協程有編程語言提供,由程序員控制進行切換,所以沒有線程安全問題,可以用來處理狀態機,並發請求等。但是無法利用多核優勢。
上面的四種方案可以配合使用,我比較看好的是進程+協程的模式。