行程

行程 在 Elixir

5 個練習

關於 行程

在 Elixir 中,所有程式碼都在行程裡執行。Elixir 的行程:

  • 不該與系統行程混為一談。
  • 很輕量。
  • 有特定的使用情境。它們可以:
  • 一個執行_數百個_行程的 Elixir 應用程式是很正常的,但完全不會明確建立新行程的應用程式也很常見,尤其是當它是個函式庫的時候。

建立行程

預設情況下,函式會在被呼叫的同一個行程中執行。當你需要明確地在新行程中執行某個函式時,可以使用 spawn:

  • spawn/1 接受一個函式,並直接執行它。

    spawn(fn -> 2 + 2 end)
    # => #PID<0.125.0>
    
  • spawn/3 接受一個函式,並透過模組名稱、函式名稱(以 atom 表示)以及要傳給該函式的引數陣列來執行它。

    spawn(String, :split, ["hello there", " "])
    # => #PID<0.113.0>
    
    • 這組資料三元組常被稱為 MFA,也就是 Module, Function, Arguments。
  • 行程會在它的函式執行完畢後立刻結束。

  • 你可以用 Process.alive?/1 檢查某個行程是否仍然_存活_(正在執行):

    pid = spawn(fn -> 2 + 2 end)
    Process.alive?(pid)
    # => false
    

訊息

行程不會彼此直接共享資訊。行程透過_傳送訊息_來共享資料。這種並行模式稱為 Actor 模型。

  • 使用 send/2 將訊息傳送給行程。

    send(pid, :hello)
    
    • 訊息會按照傳送的順序進入收件者的_信箱_。
    • send 不會檢查訊息是否已被接收,也不會檢查收件者是否還存活。
  • 訊息可以是任何型別。

  • 你可以使用 receive/1 接收傳送給目前行程的訊息。

    • 你需要對訊息進行模式比對。
    • receive 會等待,直到行程的信箱中出現符合任一模式的訊息。
      • 預設情況下它會無限期等待,但可以用 after 區塊設定逾時時間。
    • 已讀取的訊息會從行程的信箱中移除。未讀取的訊息則會無限期地留在那裡。
      • 一定要在 receive/1 中寫一個能攔截所有訊息的 _ 子句,以免因為堆積過多未讀訊息而耗盡記憶體。
    receive do
      {:ping, sender_pid} -> send(sender_pid, :pong)
      _ -> nil
    after
      5000 ->
        {:error, "No message in 5 seconds"}
    end
    

接收迴圈

如果你想接收不只一則訊息,就需要遞迴地呼叫 receive/1。常見的做法是實作一個遞迴函式,例如命名為 loop,它會呼叫 receive/1、對訊息做些處理,然後再呼叫自己以等待更多訊息。如果你需要把某些狀態從一次 receive/1 呼叫帶到下一次,可以透過傳引數給那個 loop 函式來達成。

def loop(state) do
  receive do
    :increment_by_one ->
      loop(state + 1)

    {:report_state, sender_pid} ->
      send(sender_pid, state)
      loop(state)

    :stop ->
      nil

    _ ->
      loop(state)
  end
end

實務上,很少直接使用這種做法。Elixir 提供了各種並行抽象機制,例如 Agent 模組 或 GenServer 行為,兩者都建立在接收迴圈之上。不過,要能有效率地使用這些抽象機制,理解這些基礎概念至關重要。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 行程

練習已鎖定

再解鎖 4 個練習,就能練習 行程