Elixir - 程序



在 Elixir 中,所有程式碼都在程序內執行。程序彼此隔離,併發執行,並透過訊息傳遞進行通訊。Elixir 的程序不應與作業系統程序混淆。Elixir 中的程序在記憶體和 CPU 方面極其輕量級(不像許多其他程式語言中的執行緒)。正因為如此,同時執行數萬甚至數十萬個程序並不少見。

在本章中,我們將學習生成新程序以及在不同程序之間傳送和接收訊息的基本結構。

spawn 函式

建立新程序最簡單的方法是使用spawn函式。spawn接受一個將在新程序中執行的函式。例如:

pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)

執行上述程式時,會產生以下結果:

false

spawn 函式的返回值是 PID。這是程序的唯一識別符號,因此如果您執行上面的程式碼,您的 PID 將不同。正如您在這個例子中看到的,當我們檢查程序是否還活著時,該程序已經死亡。這是因為程序一旦完成給定函式的執行就會退出。

如前所述,所有 Elixir 程式碼都在程序內執行。如果您執行 self 函式,您將看到當前會話的 PID:

pid = self
 
Process.alive?(pid)

執行上述程式時,會產生以下結果:

true

訊息傳遞

我們可以使用send向程序傳送訊息,並使用receive接收訊息。讓我們向當前程序傳遞一條訊息,並在同一程序中接收它。

send(self(), {:hello, "Hi people"})

receive do
   {:hello, msg} -> IO.puts(msg)
   {:another_case, msg} -> IO.puts("This one won't match!")
end

執行上述程式時,會產生以下結果:

Hi people

我們使用 send 函式向當前程序傳送了一條訊息,並將其傳遞給 self 的 PID。然後我們使用receive函式處理傳入的訊息。

當訊息傳送到程序時,訊息將儲存在程序郵箱中。receive 塊遍歷當前程序郵箱,搜尋與任何給定模式匹配的訊息。receive 塊支援保護和許多子句,例如 case。

如果郵箱中沒有與任何模式匹配的訊息,則當前程序將等待直到匹配的訊息到達。也可以指定超時。例如:

receive do
   {:hello, msg}  -> msg
after
   1_000 -> "nothing after 1s"
end

執行上述程式時,會產生以下結果:

nothing after 1s

注意 - 當您已經預期訊息在郵箱中時,可以給出 0 超時。

連結

Elixir 中最常見的生成方式實際上是透過spawn_link函式。在檢視使用 spawn_link 的示例之前,讓我們瞭解一下程序失敗時會發生什麼。

spawn fn -> raise "oops" end

執行上述程式時,會產生以下錯誤:

[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
   :erlang.apply/2

它記錄了一個錯誤,但生成程序仍在執行。這是因為程序是隔離的。如果我們希望一個程序中的失敗傳播到另一個程序,我們需要將它們連結起來。這可以使用spawn_link函式完成。讓我們考慮一個例子來理解這一點:

spawn_link fn -> raise "oops" end

執行上述程式時,會產生以下錯誤:

** (EXIT from #PID<0.41.0>) an exception was raised:
   ** (RuntimeError) oops
      :erlang.apply/2

如果您在iex shell 中執行此程式碼,則 shell 會處理此錯誤並且不會退出。但是,如果您首先建立一個指令碼檔案,然後使用elixir <file-name>.exs執行,則由於此錯誤,父程序也將被關閉。

在構建容錯系統時,程序和連結起著重要作用。在 Elixir 應用程式中,我們經常將程序連結到主管程序,主管程序將檢測程序何時死亡並在其位置啟動一個新程序。這隻有在程序被隔離並且預設情況下不共享任何內容時才有可能。並且由於程序是隔離的,因此一個程序中的失敗不可能導致另一個程序崩潰或損壞其狀態。雖然其他語言需要我們捕獲/處理異常;在 Elixir 中,我們實際上可以放任程序失敗,因為我們期望主管程序能夠正確地重新啟動我們的系統。

狀態

如果您正在構建一個需要狀態的應用程式,例如,保留您的應用程式配置,或者您需要解析檔案並將其儲存在記憶體中,您將在哪裡儲存它?Elixir 的程序功能在執行此類操作時非常有用。

我們可以編寫無限迴圈、維護狀態以及傳送和接收訊息的程序。例如,讓我們編寫一個模組,該模組啟動充當鍵值儲存的新程序,儲存在一個名為kv.exs的檔案中。

defmodule KV do
   def start_link do
      Task.start_link(fn -> loop(%{}) end)
   end

   defp loop(map) do
      receive do
         {:get, key, caller} ->
         send caller, Map.get(map, key)
         loop(map)
         {:put, key, value} ->
         loop(Map.put(map, key, value))
      end
   end
end

請注意,start_link函式啟動一個執行loop函式的新程序,從空對映開始。然後,loop函式等待訊息並對每條訊息執行相應的操作。對於:get訊息,它將訊息傳送回撥用方並再次呼叫 loop,以等待新訊息。而:put訊息實際上使用新的對映版本呼叫loop,其中儲存了給定的鍵值。

現在讓我們執行以下程式碼:

iex kv.exs

現在您應該在iex shell 中。要測試我們的模組,請嘗試以下操作:

{:ok, pid} = KV.start_link

# pid now has the pid of our new process that is being 
# used to get and store key value pairs 

# Send a KV pair :hello, "Hello" to the process
send pid, {:put, :hello, "Hello"}

# Ask for the key :hello
send pid, {:get, :hello, self()}

# Print all the received messages on the current process.
flush()

執行上述程式時,會產生以下結果:

"Hello"
廣告
© . All rights reserved.