天天看點

Actor并發模型&基于共享記憶體線程模型

先從著名的c10k問題談起。有一個叫Dan Kegel的人在網上(http://www.kegel.com/c10k.html)提出:現在的硬體應該能夠讓一台機器支援10000個并發的client。然後他讨論了用不同的方式實作大規模并發服務的技術,歸納起來就是兩種方式:一個client一個thread,用blocking I/O;多個clients一個thread,用nonblocking I/O或者asynchronous I/O。目前asynchronous I/O的支援在Linux上還不是很好,是以一般都是用nonblocking I/O。大多數的實作都是用epoll()的edge triggering(傳統的select()有很大的性能問題)。這就引出了thread和event之争,因為前者就是完全用線程來處理并發,後者是用事件驅動來處理并發。當然實際的系統當中往往是混合系統:用事件驅動來處理網絡時間,而用線程來處理事務。由于目前作業系統(尤其是Linux)和程式語言的限制(Java/C/C++等),線程無法實作大規模的并發事務。一般的機器,要保證性能的話,線程數量基本要限制幾百(Linux上的線程有個特點,就是達到一定數量以後,會導緻系統性能指數下降,參看SEDA的論文)。是以現在很多高性能web server都是使用事件驅動機制,比如nginx,Tornado,Node.js等等。事件驅動幾乎成了高并發的同義詞,一時間紅的不得了。

        其實線程和事件,或者說同步和異步之争早就在學術領域争了幾十年了。1978年有人為了平息争論,寫了論文證明了用線性的process(線程的模式)和消息傳遞(事件的模式)是等價的,而且如果實作合适,兩者應該有同等性能。當然這是理論上的。針對事件驅動的流行,2003年加大伯克利發表了一篇論文叫“Why events are a bad idea (for high-concurrency servers)”,指出其實事件驅動并沒有在功能上有比線程有什麼優越之處,但程式設計要麻煩很多,而且特别容易出錯。線程的問題,無非是目前的實作的原因。一個是線程占的資源太大,一建立就配置設定幾個MB的stack,一般的機器能支援的線程大受限制。針對這點,可以用自動擴充的stack,建立的先少分點,然後動态增加。第二個是線程的切換負擔太大,Linux中實際上process和thread是一回事,差別就在于是否共享位址空間。解決這個問題的辦法是用輕量級的線程實作,通過合作式的辦法來實作共享系統的線程。這樣一個是切換的花費很少,另外一個可以維護比較小的stack。他們用coroutine和nonblocking I/O(用的是poll()+thread pool)實作了一個原型系統,證明了性能并不比事件驅動差。

        那是不是說明線程隻要實作的好就行了呢。也不完全對。2006年還是加大伯克利,發表了一篇論文叫“The problem with threads”。線程也不行。原因是這樣的。目前的程式的模型基本上是基于順序執行。順序執行是确定性的,容易保證正确性。而人的思維方式也往往是單線程的。線程的模式是強行在單線程,順序執行的基礎上加入了并發和不确定性。這樣程式的正确性就很難保證。線程之間的同步是通過共享記憶體來實作的,你很難來對并發線程和共享記憶體來建立數學模型,其中有很大的不确定性,而不确定性是程式設計的巨大敵人。作者以他們的一個項目中的經驗來說明,保證多線程的程式的正确性,幾乎是不可能的事情。首先,很多很簡單的模式,在多線程的情況下,要保證正确性,需要注意很多非常微妙的細節,否則就會導緻deadlock或者race condition。其次,由于人的思維的限制,即使你采取各種消除不确定的辦法,比如monitor,transactional memory,還有promise/future,等等機制,還是很難保證面面俱到。以作者的項目為例,他們有計算機科學的專家,有最聰明的研究所學生,采用了整套軟體工程的流程:design review, code review, regression tests, automated code coverage metrics,認為已經消除了大多數問題,不過還是在系統運作4年以後,出現了一個deadlock。作者說,很多多線程的程式實際上存在并發錯誤,隻不過由于硬體的并行度不夠,往往不顯示出來。随着硬體的并行度越來越高,很多原來運作完好的程式,很可能會發生問題。我自己的體會也是,程式NPE,core dump都不怕,最怕的就是race condition和deadlock,因為這些都是不确定的(non-deterministic),往往很難重制。

        那既然線程+共享記憶體不行,什麼樣的模型可以幫我們解決并發計算的問題呢。研究領域已經發展了一些模型,目前越來越多地開始被新的程式語言采用。最主要的一個就是Actor模型。它的主要思想就是用一些并發的實體,稱為actor,他們之間的通過發送消息來同步。所謂“Don’t communicate by sharing memory, share memory by communicating”。Actor模型和線程的共享記憶體機制是等價的。實際上,Actor模型一般通過底層的thread/lock/buffer 等機制來實作,是高層的機制。Actor模型是數學上的模型,有理論的支援。另一個類似的數學模型是CSP(communicating sequential process)。早期的實作這些理論的語言最著名的就是erlang和occam。尤其是erlang,所謂的Ericsson Language,目的就是實作大規模的并發程式,用于電信系統。Erlang後來成為比較流行的語言。

繼續閱讀