Erlang - 併發



Erlang 中的併發程式設計需要遵循以下基本原則或流程。

列表包含以下原則:

piD = spawn(Fun)

建立一個新的併發程序來評估 Fun。新程序與呼叫者並行執行。示例如下:

示例

-module(helloworld). 
-export([start/0]). 

start() ->
   spawn(fun() -> server("Hello") end). 

server(Message) ->
   io:fwrite("~p",[Message]).

上述程式的輸出為:

輸出

“Hello”

Pid ! Message

將訊息傳送到識別符號為 Pid 的程序。訊息傳送是非同步的。傳送者不會等待,而是繼續執行其正在執行的操作。‘!’ 稱為傳送運算子。

示例如下:

示例

-module(helloworld). 
-export([start/0]). 
start() -> 
   Pid = spawn(fun() -> server("Hello") end), 
   Pid ! {hello}. 

server(Message) ->
   io:fwrite("~p",[Message]).

Receive…end

接收已傳送到程序的訊息。它具有以下語法:

語法

receive
Pattern1 [when Guard1] ->
Expressions1;
Pattern2 [when Guard2] ->
Expressions2;
...
End

當訊息到達程序時,系統嘗試將其與 Pattern1(可能帶有保護條件 Guard1)進行匹配;如果成功,則評估 Expressions1。如果第一個模式不匹配,則嘗試 Pattern2,依此類推。如果沒有任何模式匹配,則該訊息將儲存以供以後處理,並且程序將等待下一條訊息。

包含所有 3 個命令的整個過程示例如下程式所示。

示例

-module(helloworld). 
-export([loop/0,start/0]). 

loop() ->
   receive 
      {rectangle, Width, Ht} -> 
         io:fwrite("Area of rectangle is ~p~n" ,[Width * Ht]), 
         loop(); 
      {circle, R} ->
      io:fwrite("Area of circle is ~p~n" , [3.14159 * R * R]), 
      loop(); 
   Other ->
      io:fwrite("Unknown"), 
      loop() 
   end. 

start() ->
   Pid = spawn(fun() -> loop() end), 
   Pid ! {rectangle, 6, 10}.

關於上述程式,需要注意以下幾點:

  • loop 函式具有 receive end 迴圈。因此,當傳送訊息時,它將由 receive end 迴圈處理。

  • 產生一個新程序,該程序轉到 loop 函式。

  • 透過 Pid ! message 命令將訊息傳送到生成的程序。

上述程式的輸出為:

輸出

Area of the Rectangle is 60

最大程序數

在併發中,確定系統允許的最大程序數非常重要。然後,您應該能夠了解系統上可以併發執行多少程序。

讓我們來看一個如何確定系統上可以執行的最大程序數的示例。

-module(helloworld). 
-export([max/1,start/0]). 

max(N) -> 
   Max = erlang:system_info(process_limit), 
   io:format("Maximum allowed processes:~p~n" ,[Max]), 
   
   statistics(runtime), 
   statistics(wall_clock), 
   
   L = for(1, N, fun() -> spawn(fun() -> wait() end) end), 
   {_, Time1} = statistics(runtime), 
   {_, Time2} = statistics(wall_clock), lists:foreach(fun(Pid) -> Pid ! die end, L), 
   
   U1 = Time1 * 1000 / N, 
   U2 = Time2 * 1000 / N, 
   io:format("Process spawn time=~p (~p) microseconds~n" , [U1, U2]).
   wait() -> 
   
   receive 
      die -> void 
   end. 
 
for(N, N, F) -> [F()]; 
for(I, N, F) -> [F()|for(I+1, N, F)]. 

start()->
   max(1000), 
   max(100000).

在任何具有良好處理能力的機器上,上述兩個 max 函式都將透過。以下是上述程式的示例輸出。

Maximum allowed processes:262144
Process spawn time=47.0 (16.0) microseconds
Maximum allowed processes:262144
Process spawn time=12.81 (10.15) microseconds

帶超時的接收

有時,receive 語句可能會無限期地等待永遠不會到達的訊息。這可能是由於多種原因造成的。例如,我們的程式中可能存在邏輯錯誤,或者將要向我們傳送訊息的程序可能在傳送訊息之前已崩潰。為了避免此問題,我們可以向 receive 語句新增超時。這將設定程序等待接收訊息的最大時間。

以下是帶有指定超時的 receive 訊息的語法

語法

receive 
Pattern1 [when Guard1] -> 
Expressions1; 

Pattern2 [when Guard2] ->
Expressions2; 
... 
after Time -> 
Expressions 
end

最簡單的示例是建立一個休眠函式,如下面的程式所示。

示例

-module(helloworld). 
-export([sleep/1,start/0]). 

sleep(T) ->
   receive 
   after T -> 
      true 
   end. 
   
start()->
   sleep(1000).

上述程式碼將在實際退出之前休眠 1000 毫秒。

選擇性接收

Erlang 中的每個程序都有一個關聯的郵箱。當您向程序傳送訊息時,該訊息將放入郵箱中。只有當您的程式評估 receive 語句時,才會檢查此郵箱。

以下是選擇性 receive 語句的一般語法。

語法

receive 
Pattern1 [when Guard1] ->
Expressions1; 

Pattern2 [when Guard1] ->
Expressions1; 
... 
after 
Time ->
ExpressionTimeout 
end

這就是上述 receive 語句的工作方式:

  • 當我們進入 receive 語句時,我們啟動一個計時器(但僅當表示式中存在 after 部分時)。

  • 獲取郵箱中的第一條訊息,並嘗試將其與 Pattern1、Pattern2 等進行匹配。如果匹配成功,則從郵箱中刪除該訊息,並評估模式後面的表示式。

  • 如果 receive 語句中的任何模式都不匹配郵箱中的第一條訊息,則從郵箱中刪除第一條訊息並將其放入“儲存佇列”。然後嘗試郵箱中的第二條訊息。重複此過程,直到找到匹配的訊息或檢查完郵箱中的所有訊息。

  • 如果郵箱中的任何訊息都不匹配,則程序將被掛起,並在下次將新訊息放入郵箱時重新安排執行。請注意,當新訊息到達時,儲存佇列中的訊息不會重新匹配;僅匹配新訊息。

  • 一旦訊息匹配,則所有已放入儲存佇列的訊息都將按照它們到達程序的順序重新輸入郵箱。如果設定了計時器,則將其清除。

  • 如果我們在等待訊息時計時器超時,則評估 ExpressionsTimeout 表示式並將任何已儲存的訊息按照它們到達程序的順序放回郵箱。

廣告