實現(xiàn)單一進(jìn)程的代碼應(yīng)該包含在一個模塊中。進(jìn)程可以調(diào)用任何庫例程中的函數(shù),但是進(jìn)程的“頂層循環(huán)”代碼應(yīng)該包含在單獨的一個模塊中。進(jìn)程頂層循環(huán)的代碼不能分散在幾個模塊中——這樣做會使控制流程復(fù)雜化,變得極難理解。這并不意味著不應(yīng)該使用通用服務(wù)庫,這些庫有助于構(gòu)建控制流。
相反,應(yīng)該用單獨的一個模塊實現(xiàn)一種(不能再多了)進(jìn)程。含有不同進(jìn)程代碼的模塊會變得非常難于理解。每個獨立進(jìn)程的代碼都應(yīng)位于各自獨立的一個模塊中。
進(jìn)程是基本的系統(tǒng)構(gòu)建元素。但當(dāng)可以使用函數(shù)調(diào)用時,就不要再使用進(jìn)程和消息傳遞機(jī)制了。
注冊進(jìn)程的注冊名必須和模塊名保持相同,從而易于查找進(jìn)程代碼。
只有注冊進(jìn)程才應(yīng)該留存較長時間。
在確定是否使用順序進(jìn)程或并行進(jìn)程來實現(xiàn)時,考慮問題的本質(zhì)結(jié)構(gòu)無疑能使結(jié)論變得清晰。主要原則如下:
“使用一個并行進(jìn)程來對真實案例中的每個真正并發(fā)行為進(jìn)行建模?!?/p>
如果在實際案例中,并行處理器與真正并行的行為之間能夠建立起一對一的數(shù)量映射關(guān)系,程序就將變得易于理解。
進(jìn)程扮演著系統(tǒng)的不同角色,下面以客戶端-服務(wù)器模型為例。
一個進(jìn)程應(yīng)該盡量只擔(dān)當(dāng)一個角色,比如,它可以是服務(wù)器,也可以是客戶端,但不能將兩者混合起來。
Other roles which process might have are:
進(jìn)程可能具有的其他角色包括:
Supervisor(監(jiān)督者):查看其他進(jìn)程,如果它們失敗,則負(fù)責(zé)重啟這些進(jìn)程。
Worker(工作者):一種常見的工作進(jìn)程(有可能會出現(xiàn)錯誤)。
Trusted Worker(可信工作者):不允許出現(xiàn)錯誤。
在很多情況下,使用通用服務(wù)器程序都是一種非常好的方案,比如用標(biāo)準(zhǔn)庫實現(xiàn)的 generic 服務(wù)器。使用通用服務(wù)器會極大簡化整體的系統(tǒng)結(jié)構(gòu)。
這一點也適用于系統(tǒng)中絕大多數(shù)協(xié)議處理軟件。
所有的消息都應(yīng)該加上標(biāo)記。這樣能使接收語句的順序變得不那么重要,新消息的實現(xiàn)也容易了很多。
不要這樣編程:
loop(State) ->
receive
...
{Mod, Funcs, Args} -> % Don't do this
apply(Mod, Funcs, Args},
loop(State);
...
end.
新消息 {get_status_info, From, Option} 如果被放在 {Mod, Func, Args} 消息后面,就會引發(fā)沖突。
如果消息同步,返回的消息將用一個新的原子進(jìn)行標(biāo)記,目的是為了標(biāo)記這是返回消息。例如:假如傳入消息的標(biāo)記為 get_status_info,則返回消息標(biāo)記為status_info。另外,方便調(diào)試也是選擇不同標(biāo)記的一個理由。
下面這個方法就很不錯:
loop(State) ->
receive
...
{execute, Mod, Funcs, Args} -> % Use a tagged message.
apply(Mod, Funcs, Args},
loop(State);
{get_status_info, From, Option} ->
From ! {status_info, get_status_info(Option, State)},
loop(State);
...
end.
每個服務(wù)器都應(yīng)該在至少一個 receive 語句中保存一個 Other 的替代方案,這能避免消息隊列堵塞。范例如下:
main_loop() ->
receive
{msg1, Msg1} ->
...,
main_loop();
{msg2, Msg2} ->
...,
main_loop();
Other -> % 清空消息隊列
error_logger:error_msg(
"Error: Process ~w got unknown msg ~w~n.",
[self(), Other]),
main_loop()
end.
所有的服務(wù)器必須實現(xiàn)尾部遞歸,否則服務(wù)器就將不斷消耗系統(tǒng)內(nèi)存,直至用光它們。
不要像這樣編程:
loop() ->
receive
{msg1, Msg1} ->
...,
loop();
stop ->
true;
Other ->
error_logger:log({error, {process_got_other, self(), Other}}),
loop()
end,
io:format("Server going down"). % 不要這么做
% This is NOT tail-recursive
下面的方法才是正確的:
loop() ->
receive
{msg1, Msg1} ->
...,
loop();
stop ->
io:format("Server going down");
Other ->
error_logger:log({error, {process_got_other, self(), Other}}),
loop()
end. % This is tail-recursive
如果使用一些服務(wù)器庫,比如說generic,就不會犯下這種錯誤。
盡量在接口中利用函數(shù),而不要直接發(fā)送消息,并且要封裝傳入接口函數(shù)的消息。這其中有幾種例外情況。
消息協(xié)議是內(nèi)部消息,對其他模塊來說,它應(yīng)該是不透明的。
下面是接口函數(shù)的一個范例:
-module(fileserver).
-export([start/0, stop/0, open_file/1, ...]).
open_file(FileName) ->
fileserver ! {open_file_request, FileName},
receive
{open_file_response, Result} -> Result
end.
...<code>...
Be careful when using after in receive statements. Make sure that you handle the case when the message arrives later (See "Flush unknown messages" on page 16.).
receive 語句中使用 after 時要格外小心。一定要確保當(dāng)消息到達(dá)時再處理案例(參看 5.8 節(jié)內(nèi)容)。
盡量減少捕獲退出信號的進(jìn)程數(shù)目。進(jìn)程要么捕獲退出,要么就根本不捕獲。在實際編碼時,讓進(jìn)程對是否捕獲退出進(jìn)行“切換”,是非常糟糕的一種實踐。