本文為csdn I_myours 原創,此處為轉載。作者秉持了 free, open, share 的原則,但敬請各位在轉載的時候自覺標明原創作者及出處,這是各位對作者勞動成果的自覺尊重!
作者:I_myours
原文:http://blog.csdn.net/wwh578867817/article/details/49774169
首先,我們聊聊現實世界中的并發。
我曾經舉過一個并發和并行的例子:
老媽在很短的時間給我安排了很多任務,吃包子,拖地,洗碗…等等
由于我母親大人比較嚴厲,所以我“愉快”地接受了所有任務。
這就是所謂的并發,在某一時段,能接受的事件。
我只有兩只手,可以用一只手來吃包子,另一只手來拖地。
這就是所謂的并行,在某一時刻,能處理的事件。
現實世界中,并發的事情無處不在,就拿上面的例子來說,我答應了老媽的多個要求,老媽也收到了我的回復。這說明我們的大腦天生就是并發的,它可以在某一時段接受大量的信息。

所以,符合我們思維的并發應該如上圖,我可以接收老媽的多個消息,即使老爸回來給我發消息,我也能接收。

這是現實世界中的并發,人與人之間是單獨的個體,通過發送消息進行交流。
此時,你可能已經猜到這就是Erlang 中的并發,Actor模型的樣子。
讓我們先跨過這個,來看看傳統的并發是如何做的。
共享內存
在我們平時寫的程序中,并發事件處理一般是通過多線程或多進程來處理。
某一時刻,我們同時接收到了多條請求,通常的做法是將它放入隊列(共用的內存)中去,每一個線程或者進程都會去隊列中取消息進行處理。
如下圖:

此時,隊列中的內存是共享的,多個線程或進程存取會造成竟態條件,也就是產生競爭,發生錯誤。
通常的做法是 加鎖。

線程或進程必須先搶到鎖,接著搶到鎖的才能訪問隊列中的消息。
注意,鎖是錯誤的源泉。
我們應該都遇到過死鎖等錯誤,先不說性能,調試起來就很麻煩。
那么 無鎖 CAS 呢?

每個線程或進程都先取出一條消息,保存舊值,拷貝一份后修改為新值,將自己保存的舊值和原先隊列中的值比較,若相同說明沒有被其它線程或進程修改,則這條消息屬于該線程或進程,可以處理此消息。
若舊值和新值不同,說明有其它線程或進程得到了此消息,則循環進行下一次 CAS 操作。
這就是所謂的copy and set。
這里我們不對比這兩種方式以及一會說的 Actor 模型性能的好壞。
因為在不同的場景下,不同的方式性能也是不同的。我們需要根據具體情況,測試,分析,從而得出性能最優的方式。
順便提一句,Actor只是模型,僅僅表現給我們的是消息傳遞。至于它內部怎樣實現這里不討論,感興趣可以看看內部實現
不過從剛才的描述來看,上述方式都不符合我們的思維,而且略復雜,相信沒有人是通過 共享大腦 來傳遞及處理消息的吧?
來看看 Actor 模型
Actor 模型概念非常簡單,且非常符合我們的思維。
萬物皆為 Actor,Actor 與 Actor 之間通過發送消息來通信。
就和人類一般,一個人是一個 Actor,人與人之間通過消息來交互。

別驚訝,Actor 模型就是這么簡單。(Actor 模型更多細節參見 Wiki Actor)
接著我們來看 Erlang 是如何運用 Actor 模型的。
Erlang 的 Actor 模型也非常簡單。
在 Erlang 中,進程為最小的單位,也就是所謂的 Actor。注意 Erlang 的進程不是我們傳統上的進程,它運行在 Erlang 虛擬機上,非常小,非常輕,可以瞬間創建上萬,甚至幾十萬個,進程間完全是獨立的,不共享內存。在進程運行時若出現錯誤,由于進程的輕量級,Erlang 采取的措施是“讓其他進程修復”和“任其崩潰”。在 Erlang 上查看默認限制數量是26萬多,可以進行修改。每個進程創建后都會有一個獨一無二的 Pid,這些進程之間通過 Pid 來互相發送消息,進程的唯一交互方式也是消息傳遞,消息也許能被對方收到,也許不能,收到后可以處理該消息。如果想知道某個消息是否被進程收到,必須向該進程發送一個消息并等待回復。
Erlang 中的并發編程只需要如下幾個簡單的函數。
Pid = spawn(Mod,Func, Args)
創建一個新的并發進程來執行Mod模塊中的 Fun(),Args 是參數。
Pid ! Message
想序號為 Pid 的進程發送消息。消息發送是異步的,發送方不等待而是繼續之前的工作。
receive… end
接受發送給某個進程的消息,匹配后處理。
receive
Pattern 1 [when Guard1] ->
Expression1;
Pattern 2 [when Guard2] ->
Expression2;
...
after T ->
ExpressionTimeout
某個消息到達后,會先與 Pattern 進行匹配,匹配相同后執行,若未匹配成功消息則會保存起來待以后處理,進程會開始下一輪操作,若等待超時 T,則會執行表達式 ExpressionTimeout。
舉個例子:
現在我們要進行兩個進程的消息傳遞,一個進程發送Num1 和 Num2以及對應的操作標識,另外一個進程接受到消息后計算。
比如 {plus, Num1, Num2} 就是求 Num1 和 Num2 的和。
-module(calculate).
-export([loop/0, start/0]).
start() -> spawn(calculate, loop, []).
loop() ->
receive
{plus, Num1, Num2} ->
io:format("Num1 plus Num2 result:~p~n", [Num1 + Num2]),
loop();
{reduce, Num1, Num2} ->
io:format("Num1 reduce Num2 result:~p~n", [Num1 - Num2]),
loop();
{multi, Num1, Num2} ->
io:format("Num1 multi Num2 result:~p~n", [Num1 * Num2]),
loop();
{divis, Num1, Num2} ->
io:format("Num1 divis Num2 result:~p~n", [Num1 div Num2]),
loop()
after 50000 ->
io:format("timeout!!")
end().
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
執行結果:

我們一行一行來看。
第一行(標號為 1>),編譯 calculate.erl。
第二行,執行 calculate 模塊中的 start 函數并且創建進程,創建后將進程的 Pid 賦值給 Pid1。
第三行,打印 Pid1。
第四行,創建 Pid2 的進程并且向 Pid1 進程發送請求消息,計算 Num1 和 Num2 的和,Pid1 進程計算完畢后并打印。
第五行,計算減法并打印。
第六行,報錯因為 Pid3 已經被使用,在 Erlang 中變量賦值一次后就不能被改變了,不會變就不會出現多個進程修改導致不一致問題。
第七行,計算乘法并打印結果。
第八行,計算除法并打印結果。
最后,超時發生錯誤。
由此可見 Erlang 并發編程也非常簡單且符合人們的思維。
這篇文章就簡單地介紹到這里,希望有所幫助^_^。
posted on 2016-12-16 12:20
思月行云 閱讀(559)
評論(0) 編輯 收藏 引用 所屬分類:
Erlang