Erlang的并發(fā)是基于消息傳遞和Actor模型的
在Erlang中檐春,并發(fā)(Concurrncy)指的是有許多獨(dú)立運(yùn)行的
actor
组橄,但是并不要求它們同時(shí)運(yùn)行率拒,而并行(Parallelism)指的是多個(gè)actor
在同時(shí)運(yùn)行Erlang對(duì)可靠性要求很高贸弥,因此采用了一種最徹底的做法,禁止進(jìn)程之間共享內(nèi)存
因?yàn)樵诔霈F(xiàn)崩潰之后弄息,共享內(nèi)存會(huì)導(dǎo)致系統(tǒng)中的狀態(tài)不一致痊班,使問題復(fù)雜化
與共享內(nèi)存的方式不同,進(jìn)程之間只能通過發(fā)送消息進(jìn)行通信摹量,所有的消息數(shù)據(jù)都是復(fù)制的涤伐。這種方式效率會(huì)低一點(diǎn),但是更安全
當(dāng)系統(tǒng)中的某個(gè)部分出現(xiàn)了錯(cuò)誤荆永,造成了數(shù)據(jù)破壞废亭,那么這個(gè)部分應(yīng)該盡快死亡以防止錯(cuò)誤和壞數(shù)據(jù)傳播到系統(tǒng)的剩余部分
Erlang通過在VM中實(shí)現(xiàn)進(jìn)程,這樣實(shí)現(xiàn)者們可以對(duì)優(yōu)化和可靠性進(jìn)行完全掌控
一個(gè)Erlang進(jìn)程大概占用300個(gè)字的內(nèi)存空間具钥,創(chuàng)建時(shí)間只有幾微妙
為了管理程序所創(chuàng)建的所有進(jìn)程豆村,VM會(huì)為每個(gè)核啟動(dòng)一個(gè)線程來充當(dāng)一個(gè)調(diào)度器(scheduler)
每個(gè)調(diào)度器有一個(gè)運(yùn)行隊(duì)列(run queue),也就是一個(gè)Erlang進(jìn)程列表骂删,會(huì)給其中的每個(gè)進(jìn)程分配一小段運(yùn)行時(shí)間片
當(dāng)某個(gè)調(diào)度器的運(yùn)行隊(duì)列中任務(wù)過多時(shí)掌动,會(huì)把一部分任務(wù)遷移到其他隊(duì)列中。這意味著宁玫,每個(gè)Erlang VM都會(huì)進(jìn)行負(fù)載均衡操作粗恢,程序員無需關(guān)心
Erlang并發(fā)編程需要3個(gè)原語:創(chuàng)建(
spawn
)進(jìn)程、發(fā)送消息及接收消息在Erlang中進(jìn)程就是一個(gè)函數(shù)欧瘪。進(jìn)程運(yùn)行一個(gè)函數(shù)眷射,一般運(yùn)行結(jié)束,進(jìn)程就消失了
-
要啟動(dòng)一個(gè)新進(jìn)程,可以使用Erlang提供的函數(shù)
spawn/1
妖碉,這個(gè)函數(shù)以一個(gè)函數(shù)為參數(shù)涌庭,并運(yùn)行它> F = fun() -> 2 + 2 end. > spawn(F). <0.82.0>
spawn/1
的返回值(<0.82.0>
)稱為進(jìn)程標(biāo)識(shí)符,通常寫成pid
欧宜、Pid
或PID
pid
是一個(gè)隨意設(shè)定的值坐榆,用來表示虛擬機(jī)運(yùn)行期間的某個(gè)時(shí)間點(diǎn)上存在(或曾經(jīng)存在)的某個(gè)進(jìn)程可以用
pid
作為地址進(jìn)行進(jìn)程間的通信在上面的例子中,我們無法得到函數(shù)
F
的返回值冗茸。我們只能得到它的pid
席镀。因?yàn)檫M(jìn)程不會(huì)返回任何東西使用BIF的
self/0
函數(shù),可以返回當(dāng)前進(jìn)程的pid
-
Erlang的消息傳遞原語——操作符
!
夏漱,也稱為bang符號(hào)豪诲。該操作符的左邊是一個(gè)pid
,右邊可以是任意Erlang數(shù)據(jù)項(xiàng)麻蹋。這個(gè)數(shù)據(jù)項(xiàng)會(huì)被發(fā)送給左邊的pid
所代表的進(jìn)程跛溉,這個(gè)進(jìn)程就可以訪問它了> self() ! hello.
-
消息會(huì)被放到接收進(jìn)程的郵箱中,但是并沒有被讀取扮授。上面例子中出現(xiàn)的第二個(gè)hello是這個(gè)發(fā)送函數(shù)的返回值。這意味著专肪,可以用如下方式給多個(gè)進(jìn)程發(fā)送同樣的消息
> self() ! self() ! double
-
進(jìn)程郵箱中的消息是按照接收順序保存的刹勃,每當(dāng)讀取一個(gè)消息時(shí),就會(huì)把消息從郵箱中取出
> flush(). Shell got hello Shell got double Shell got double ok
flush/0
函數(shù)只是一種輸出所收到的消息的快捷方法-
使用
receive
表達(dá)式來接收消息嚎尤。receive
的語法和case...of
非常相似荔仁。事實(shí)上,它們的模式匹配部分的工作原理完全一樣芽死,只是receive
模式中變量會(huì)綁定到收到的消息乏梁,而不是case
和of
之間的表達(dá)式。receive
表達(dá)式也可以有衛(wèi)語句receive Pattern1 when Guard1 -> Expr1; Pattern2 when Guard2 -> Expr2; Pattern3 -> Expr3 end
要想知道進(jìn)程是否收到了消息关贵,唯一的方法是讓它發(fā)送一條回應(yīng)遇骑。我們的進(jìn)程如果需要知道要把回應(yīng)發(fā)送給誰,就必須在消息中添加我們的
pid
揖曾。在Erlang中落萎,我們通過把進(jìn)程
pid
打包在一個(gè)元組中完成這項(xiàng)工作,如果不這樣做炭剪,那么消息就都是匿名的练链。打包的結(jié)果是一條類似{Pid, Message}
的消息-
我們來編寫一個(gè)海豚程序來展示消息的收發(fā)
-module(dolphins). -compile(export_all). dolphin() -> receive {From, do_a_flip} -> From ! "How about no?", dolphin(); {From, fish} -> From ! "So long and thanks for all the fish!"; _ -> io:format("Heh, we're smarter than you humans.~n"), dolphin() end.
Eshell > Dolphin = spawn(dolphins, dolphin, []). <0.85.0> > Dolphin ! {self(), do_a_flip}. {<0.78.0>,do_a_flip} > Dolphin ! {self(), unknown_message}. Heh, we're smarter than you humans. {<0.78.0>,unknown_message} > Dolphin ! {self(), fish}. {<0.78.0>,fish} > flush(). Shell got "How about no?" Shell got "So long and thanks for all the fish!" ok
在上面的測(cè)試中,引入了一個(gè)新的進(jìn)程創(chuàng)建函數(shù)
spawn/3
奴拦。不再只以一個(gè)函數(shù)為參數(shù)媒鼓,spawn/3
函數(shù)有3個(gè)參數(shù):模塊、函數(shù)、和函數(shù)參數(shù)如果進(jìn)程和
actor
只是一些能收發(fā)消息的函數(shù)绿鸣,并不會(huì)帶來多少好處疚沐。為了能夠得到更大的好處,需要在進(jìn)程中持有狀態(tài)借助于遞歸函數(shù)的幫助枚驻,進(jìn)程的狀態(tài)可以全部存放到遞歸函數(shù)的參數(shù)中
如果直接使用消息的收發(fā)濒旦,程序員則需要知道每個(gè)進(jìn)程自身使用的協(xié)議。這是一個(gè)無意義的負(fù)擔(dān)再登。
-
一種好的方式是尔邓,使用函數(shù)來處理消息的接收和發(fā)送,從而把消息隱藏起來
store(Pid, Food) -> Pid ! {self(), {store, Food}}, receive {Pid, Msg} -> Msg end.
-
同樣Erlang中也習(xí)慣在模塊中锉矢,增加一個(gè)
start/1
函數(shù)來隱藏進(jìn)程啟動(dòng)start() -> spawn(?MODULE, dolphin, []).
?MODULE
是一個(gè)宏梯嗽,它的值是當(dāng)前模塊的名字-
receive
可以使用after
子句來處理超時(shí)receive Match -> Expression1 after Delay -> Expression2 end
當(dāng)過了
Delay
(單位:毫秒)時(shí)間后,還沒有收到和Match
模式相匹配的消息沽损,就會(huì)執(zhí)行after
部分實(shí)際上
after
除了可以接收毫秒值外灯节,還可以接收原子infinity
在大多數(shù)語言中,異常都是使用
try...catch
這種方式在程序執(zhí)行流內(nèi)處理的這種常見的做法存在一個(gè)問題绵估,要么必須在正常代碼邏輯的每一層中處理異常錯(cuò)誤炎疆,要么只好把錯(cuò)誤處理的負(fù)擔(dān)一直推到程序的最頂層中處理。這樣做雖然可以捕獲所有的錯(cuò)誤国裳,但卻再也無法知道錯(cuò)誤出現(xiàn)的原因了
Erlang除了支持常見的異常處理模式形入,還支持另一種層次的異常處理》熳螅可以把異常處理邏輯從程序的正常執(zhí)行流中移出來亿遂,放到另外一個(gè)并發(fā)進(jìn)程中。這種方法會(huì)讓代碼更加整潔渺杉,只用考慮那些“正常的情況”
鏈接(link)是兩個(gè)進(jìn)程之間的一種特殊關(guān)系蛇数。當(dāng)兩個(gè)進(jìn)程間建立了這種關(guān)系后,如果其中一個(gè)進(jìn)程由于意外的拋出是越、出錯(cuò)或者退出而死亡時(shí)耳舅,另外一個(gè)進(jìn)程也會(huì)死亡,把這兩個(gè)進(jìn)程獨(dú)立的生存期綁定成一個(gè)關(guān)聯(lián)在一起的生存期
從盡快失敗阻止錯(cuò)誤蔓延的角度來說英妓,這是一個(gè)非常有用的概念挽放。如果某個(gè)進(jìn)程由于錯(cuò)誤崩潰了,但依賴于它的進(jìn)程卻繼續(xù)運(yùn)行蔓纠,那么所有這些依賴進(jìn)程都必須要處理依賴缺失情況辑畦。讓它們死亡,然后重啟整個(gè)進(jìn)程組通常是一種可以接受的替代方案腿倚。鏈接就是實(shí)現(xiàn)這種功能的
Erlang中又一個(gè)原生函數(shù)
link/1
纯出,用于在兩個(gè)進(jìn)程間建立一條鏈接,它的參數(shù)是進(jìn)程的pid
。當(dāng)調(diào)用它時(shí)暂筝,會(huì)在當(dāng)前進(jìn)程和參數(shù)pid
標(biāo)識(shí)的進(jìn)程之間建立一條鏈接箩言。要去除鏈接可以使用unlink/1
-
當(dāng)鏈接進(jìn)程中的一個(gè)死亡時(shí),會(huì)發(fā)送一條特殊的消息焕襟,其中含有死亡原因相關(guān)的信息陨收。如果進(jìn)程正常死亡了(函數(shù)執(zhí)行完畢),就不會(huì)發(fā)送這條消息
-module(linkmon). -compile(export_all). myproc() -> timer:sleep(5000), exit(reason).
Eshell > c(linkmon). > spawn(fun linkmon:myproc/0). > link(spawn(fun linkmon:myproc/0)). true ** exception error: reason
注意鸵赖!鏈接不會(huì)堆疊务漩,如果在同樣的兩個(gè)進(jìn)程之間調(diào)用了多次
link/1
,那么這兩個(gè)進(jìn)程之間只會(huì)存在一條鏈接它褪,只需一次unlink/1
調(diào)用就可以解除這個(gè)鏈接-
link(spawn(Function))
或者link(spawn(M, F, A))
并不是一個(gè)原子操作饵骨。有時(shí)進(jìn)程會(huì)在鏈接建立成功之前死亡,從而導(dǎo)致不期望的行為茫打。因此居触,Erlang中增加了spawn_link/1-3
函數(shù)。這個(gè)函數(shù)的參數(shù)和spawn/1-3
完全一樣老赤,創(chuàng)建一個(gè)進(jìn)程轮洋,并和它建立鏈接,就像使用了link/1
一樣抬旺,不過這是一個(gè)原子調(diào)用(兩個(gè)操作被合并成一個(gè)操作砖瞧,要么成功,要么失敗嚷狞,不會(huì)出現(xiàn)其他情況)> spawn_link(fun linkmon:myproc/0). <0.90.0> ** exception error: reason
跨進(jìn)程的錯(cuò)誤傳播對(duì)進(jìn)程來說和消息傳遞類似,不過使用的是一種稱為信號(hào)(signal)的特殊消息荣堰。退出信號(hào)是一種“秘密”消息床未,會(huì)自動(dòng)作用到進(jìn)程上并殺死它們
鏈接可以完成快速殺死進(jìn)程的工作,還缺少快速重啟部分振坚。要重啟一個(gè)進(jìn)程薇搁,首先需要知道它已經(jīng)死亡了,有一種稱為系統(tǒng)進(jìn)程的概念渡八,可以完成這項(xiàng)工作
-
系統(tǒng)進(jìn)程就是一般的進(jìn)程啃洋,只是它們可以把退出信號(hào)轉(zhuǎn)換成普通的消息。進(jìn)程可以通過調(diào)用
process_flag(trap_exit, true)
實(shí)現(xiàn)這一點(diǎn)> process_flag(trap_exit, true). > spawn_link(fun linkmon:myproc/0). > receive X -> X end. {'EXIT',<0.97.0>,reason}
也許殺死進(jìn)程并不是你想要的屎鳍,也許你只想當(dāng)一個(gè)跟蹤者宏娄。如果是這樣,那么監(jiān)視器(monitor)可能就是你想要的
監(jiān)控器是一種特殊類型的鏈接
監(jiān)控器是單向的
在兩個(gè)進(jìn)程之間可以設(shè)置多個(gè)監(jiān)控器(監(jiān)控器可以疊加逮壁,每個(gè)監(jiān)控器有自己的標(biāo)識(shí))
如果一個(gè)進(jìn)程想知道另外一個(gè)進(jìn)程的死活孵坚,但是這兩個(gè)進(jìn)程之間并沒有強(qiáng)的業(yè)務(wù)關(guān)聯(lián)時(shí),可以使用監(jiān)視器
-
創(chuàng)建監(jiān)控器的函數(shù)是
erlang:monitor/2
,它的第一個(gè)參數(shù)永遠(yuǎn)是原子process
卖宠,第二個(gè)參數(shù)是pid
> erlang:monitor(process, spawn(fun() -> timer:sleep(500) end)). > flush(). Shell got {'DOWN',#Ref<0.4159903409.3575906310.207444>,process,<0.80.0>,normal}
每當(dāng)被監(jiān)控的進(jìn)程死亡時(shí)巍杈,監(jiān)控進(jìn)程都會(huì)收到一條消息,格式是
{'DOWN', MonitorReference, process, Pid, Reason}
扛伍。其中的引用可以用來解除對(duì)一個(gè)進(jìn)程的監(jiān)控記卓昶琛!監(jiān)控器是可以疊加的刺洒,因此會(huì)收到多條
DOWN
消息鳖宾。引用可以唯一確定一條DOWN
消息-
和鏈接一樣,監(jiān)控器也有一個(gè)原子性質(zhì)的函數(shù)作媚,可以在創(chuàng)建進(jìn)程的同時(shí)監(jiān)控它:
spawn_monitor/1-3
> {Pid, Ref} = spawn_monitor(fun() -> receive _ -> exit(boom) end end). > erlang:demonitor(Ref). > Pid ! dir. > flush().
-
這個(gè)例子我們?cè)谶M(jìn)程死亡前解除了對(duì)它的監(jiān)控攘滩,因此無法跟蹤到它的死亡消息。還有另一個(gè)函數(shù)
demonitor/2
纸泡,它的功能會(huì)多一點(diǎn)漂问。第二個(gè)參數(shù)是一個(gè)選項(xiàng)列表。不過女揭,只有兩個(gè)可用選項(xiàng):info
和flush
> erlang:demonitor(Ref, [flush, info]). false
info
選項(xiàng)用來指示某個(gè)監(jiān)控器在解除時(shí)是否還存在蚤假。這也是為何這里調(diào)用返回了false
flush
選項(xiàng)會(huì)把郵箱中存在的DOWN
消息都清除掉Erlang還為進(jìn)程提供了一個(gè)命名的方法。通過給進(jìn)程起一個(gè)名字吧兔,可以用一個(gè)原子而不是一個(gè)不可理解的
pid
來標(biāo)識(shí)一個(gè)進(jìn)程磷仰。可以使用這個(gè)原子名給進(jìn)程發(fā)送消息境蔼,和pid
完全一樣可以使用函數(shù)
erlang:register(Name, Pid)
為進(jìn)程命名灶平。如果進(jìn)程死亡,它會(huì)自動(dòng)失去自己的名字箍土。也可以使用函數(shù)unregister/1
手工解除進(jìn)程的名字注冊(cè)可以調(diào)用
registered/0
得到所有已注冊(cè)進(jìn)程的列表逢享,或者通過Eshell命令regs()
得到更詳細(xì)的信息通過函數(shù)
whereis/1
可以獲取已注冊(cè)進(jìn)程的pid
如果有一個(gè)數(shù)據(jù)可以被多個(gè)進(jìn)程看到,這就是大家熟知的共享狀態(tài)
如果多個(gè)不同進(jìn)程同時(shí)訪問數(shù)據(jù)吴藻、修改數(shù)據(jù)的內(nèi)容瞒爬,導(dǎo)致信息不一致,發(fā)生軟件錯(cuò)誤沟堡。對(duì)這種情況有一個(gè)常用術(shù)語:競(jìng)爭(zhēng)條件(race condition)
競(jìng)爭(zhēng)條件非常危險(xiǎn)侧但,因?yàn)樗鼈兊某霈F(xiàn)依賴于事件的時(shí)序。在幾乎所有現(xiàn)存的并發(fā)和并行語言中航罗,這種時(shí)序都和一些不可預(yù)測(cè)的因素有關(guān)禀横,如處理器的繁忙程度、進(jìn)程運(yùn)行的位置以及程序所處理的數(shù)據(jù)類型
-
在實(shí)際使用Erlang收發(fā)消息時(shí)伤哺,我們應(yīng)該通過引用(
make_ref()
)來作為識(shí)別消息的唯一值燕侠,并用它來保證從正確的進(jìn)程收到了正確的消息judge2(Band, Album) -> Ref = make_ref(), critic ! {self(), Ref, {Band, Album}}, receive {Ref, Criticism} -> Criticism after 2000 -> timeout end. critic2() -> receive {From, Ref, {_Band, _Album}} -> From ! {Ref, "They are terrible!"} end, critic2().
最后請(qǐng)記住者祖,原子的個(gè)數(shù)是有限的。絕對(duì)不要?jiǎng)討B(tài)創(chuàng)建原子绢彤。這意味著七问,命名進(jìn)程應(yīng)該保留給那些單個(gè)VM實(shí)例中唯一的、重要的并且在整個(gè)應(yīng)用運(yùn)行期間都要一直存在的服務(wù)茫舶。如果需要為那些暫時(shí)的或者VM中并不唯一的進(jìn)程命名械巡,就意味著可能需要把它們看成一個(gè)群組。明智的做法是把它們鏈接在一起饶氏,讓它們共存亡讥耗,而不是試圖使用動(dòng)態(tài)的名字
Erlang極簡(jiǎn)學(xué)習(xí)筆記<09>——進(jìn)程篇
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
- 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來节仿,“玉大人晤锥,你說我怎么就攤上這事±认埽” “怎么了矾瘾?”我有些...
- 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)箭启。 經(jīng)常有香客問我霜威,道長(zhǎng),這世上最難降的妖魔是什么册烈? 我笑而不...
- 正文 為了忘掉前任,我火速辦了婚禮婿禽,結(jié)果婚禮上赏僧,老公的妹妹穿的比我還像新娘。我一直安慰自己扭倾,他們只是感情好淀零,可當(dāng)我...
- 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膛壹,像睡著了一般驾中。 火紅的嫁衣襯著肌膚如雪唉堪。 梳的紋絲不亂的頭發(fā)上,一...
- 文/蒼蘭香墨 我猛地睜開眼割卖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了患雏?” 一聲冷哼從身側(cè)響起鹏溯,我...
- 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淹仑,沒想到半個(gè)月后丙挽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
- 正文 獨(dú)居荒郊野嶺守林人離奇死亡攻人,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
- 正文 我和宋清朗相戀三年取试,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怀吻。...
- 正文 年R本政府宣布屑咳,位于F島的核電站萨赁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏兆龙。R本人自食惡果不足惜杖爽,卻給世界環(huán)境...
- 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望紫皇。 院中可真熱鬧慰安,春花似錦、人聲如沸聪铺。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽铃剔。三九已至撒桨,卻和暖如春查刻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凤类。 一陣腳步聲響...
- 正文 我出身青樓火欧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親茎截。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苇侵,可洞房花燭夜當(dāng)晚...
推薦閱讀更多精彩內(nèi)容
- Erlang是一門函數(shù)式編程語言。Erlang的核心特征是容錯(cuò)企锌,并發(fā)只是容錯(cuò)這個(gè)約束下的一個(gè)副產(chǎn)品 對(duì)于同樣的參數(shù)...
- 并發(fā) 創(chuàng)建進(jìn)程 使用 erlang:spawn/1,2,3,4 用來創(chuàng)建一個(gè) erlang 進(jìn)程榆浓。Erlang 進(jìn)...
- 多進(jìn)程 Elixir強(qiáng)大的并發(fā)來自其actor并發(fā)模型,簡(jiǎn)而言之就是可以使用大量的進(jìn)程來實(shí)現(xiàn)并發(fā)撕攒。elixir中的...