11-程式
11-程式
程式派生
傳送和接收
連結
狀態
Elixir中,所有程式碼都在程式中執行。程式獨立於彼此,一個接一個併發執行,彼此通過訊息傳遞來溝通。 程式不僅僅是Elixir中最基本的併發單位,它們還是Elixir建立分散式和高容錯程式的基礎。
Elixir的程式和作業系統中的程式不可混為一談。 Elixir的程式非常非常地輕量級(在使用CPU和記憶體角度上說。但是它又不同於其它語言中的執行緒)。 因此,同時執行著數萬個程式也並不是罕見的事。
本章將講解派生新程式的基本知識,以及不同程式間傳送和接受訊息。
11.1-程式派生
派生(spawning)一個新程式的方法是使用自動匯入的spawn/1
函式:
iex> spawn fn -> 1 + 2 end
#PID<0.43.0>
spawn/1
接收一個函式型別作為引數,在另一個程式中執行它。
注意spawn/1返回一個PID(程式標識)。在這個時候,你派生的這個程式很可能已經死了。 派生的程式執行完函式後便會結束:
iex> pid = spawn fn -> 1 + 2 end
#PID<0.44.0>
iex> Process.alive?(pid)
false
你可能會得到與例子中不一樣的PID
用self/0
函式獲取當前程式的PID:
iex> self()
#PID<0.41.0>
iex> Process.alive?(self())
true
可以傳送和接收訊息,程式變得越來越有趣。
11.2-傳送和接收
使用send/2
函式傳送訊息,用receive/1
接收訊息:
iex> send self(), {:hello, "world"}
{:hello, "world"}
iex> receive do
...> {:hello, msg} -> msg
...> {:world, msg} -> "won't match"
...> end
"world"
當一條訊息被髮給某程式,該訊息被儲存在該程式的郵箱裡。receive/1
語句塊
檢查當前程式的郵箱尋找匹配給定模式的訊息。receive/1
函式支援很多子句,如case/2
。
也可以給子句加上衛兵表示式。
如果找不到匹配的訊息,當前程式將一直等待,知道下一條資訊到達。可以設定一個超時時間:
iex> receive do
...> {:hello, msg} -> msg
...> after
...> 1_000 -> "nothing after 1s"
...> end
"nothing after 1s"
超時時間設為0表示你知道當前郵箱內肯定有郵件存在,很自信,設這個極短的超時時間。
把以上概念綜合起來,演示程式間傳送訊息:
iex> parent = self()
#PID<0.41.0>
iex> spawn fn -> send(parent, {:hello, self()}) end
#PID<0.48.0>
iex> receive do
...> {:hello, pid} -> "Got hello from #{inspect pid}"
...> end
"Got hello from #PID<0.48.0>"
在shell中執行程式時,輔助函式flush/0
很有用。它清空緩衝區,列印程式郵箱中的所有訊息:
iex> send self(), :hello
:hello
iex> flush()
:hello
:ok
11.3-連結
Elixir中最常用的程式派生方式是通過函式spawn_link/1
。
在舉例子講解spawn_link/1
之前,來看看如果一個程式失敗了會發生什麼:
iex> spawn fn -> raise "oops" end
#PID<0.58.0>
。。。啥也沒發生。這時因為程式都是互不干擾的。如果我們希望一個程式中發生失敗可以被另一個程式知道,我們需要連結它們。
使用spawn_link/1
函式,例子:
iex> spawn_link fn -> raise "oops" end
#PID<0.60.0>
** (EXIT from #PID<0.41.0>) an exception was raised:
** (RuntimeError) oops
:erlang.apply/2
當失敗發生在shell中,shell會自動終止執行,並顯示失敗資訊。這導致我們沒法看清背後過程。
要弄明白連結的程式在失敗時發生了什麼,我們在一個指令碼檔案使用spawn_link/1
並且執行和觀察它:
# spawn.exs
spawn_link fn -> raise "oops" end
receive do
:hello -> "let's wait until the process fails"
end
這次,失敗的程式在失敗時把它的父程式也弄停止了,因為它們是連結的。
手動連結程式:Process.link/1
。
建議可以多看看Process模組,裡面包含很多常用的程式操作函式。
程式和連結在建立能容忍失敗的系統時扮演重要角色。在Elixir程式中,我們經常把程式連結到某“管理者”上。
由這個角色負責檢測失敗程式,並且建立新程式取代之。因為程式間獨立,預設情況下不共享任何東西。
而且當一個程式失敗了,也不會影響其它程式。
因此這種形式(程式連結到“管理者”角色)是唯一的實現方法。
其它語言通常需要我們來try-catch異常,而在Elixir中我們對此無所謂,放手任程式掛掉。
因為我們希望“管理者”會以更合適的方式重啟系統。
“要死你就快一點”是Elixir軟體開發的通用哲學。
在講下一章之前,讓我們來看一個Elixir中常見的建立程式的情形。
11.4-狀態
目前為止我們還沒有怎麼談到狀態。但是,只要你建立程式,就需要狀態。 例如,儲存程式的配置資訊,或者分析一個檔案先把它儲存在記憶體裡。 你怎麼儲存狀態?
程式就是(最常見的)答案。我們可以寫無限迴圈的程式,儲存一個狀態,然後通過收發資訊來告知或改變該狀態。 例如,寫一個模組檔案,用來建立一個提供k-v倉儲服務的程式:
defmodule KV do
def start do
{:ok, spawn_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
函式簡單地派生一個新程式,這個程式以一個空的圖為引數,執行loop/1
函式。
這個loop/1
函式等待訊息,並且針對每個訊息執行合適的操作。
加入受到一個:get
訊息,它把訊息發回給呼叫者,然後再次呼叫自身loop/1
,等待新訊息。
當受到:put
訊息,它便用一個新版本的圖變數(裡面的k-v更新了)再次呼叫自身。
執行一下試試:
iex> {:ok, pid} = KV.start
#PID<0.62.0>
iex> send pid, {:get, :hello, self()}
{:get, :hello, #PID<0.41.0>}
iex> flush
nil
一開始程式內的圖變數是沒有鍵值的,所以傳送一個:get
訊息並且重新整理當前程式的收件箱,返回nil。
下面再試試傳送一個:put
訊息:
iex> send pid, {:put, :hello, :world}
#PID<0.62.0>
iex> send pid, {:get, :hello, self()}
{:get, :hello, #PID<0.41.0>}
iex> flush
:world
注意程式是怎麼保持一個狀態的:我們通過同該程式收發訊息來獲取和更新這個狀態。 事實上,任何程式只要知道該程式的PID,都能讀取和修改狀態。
還可以註冊這個PID,給它一個名稱。這使得人人都知道它的名字,並通過名字來向它傳送訊息:
iex> Process.register(pid, :kv)
true
iex> send :kv, {:get, :hello, self()}
{:get, :hello, #PID<0.41.0>}
iex> flush
:world
使用程式維護狀態,以及註冊程式都是Elixir程式非常常用的方式。 但是大多數時間我們不會自己實現,而是使用Elixir提供的抽象實現。 例如,Elixir提供的agent就是一種維護狀態的簡單的抽象實現:
iex> {:ok, pid} = Agent.start_link(fn -> %{} end)
{:ok, #PID<0.72.0>}
iex> Agent.update(pid, fn map -> Map.put(map, :hello, :world) end)
:ok
iex> Agent.get(pid, fn map -> Map.get(map, :hello) end)
:world
給Agent.start/2
方法加一個一個:name
選項可以自動為其註冊一個名字。
除了agents,Elixir還提供了建立通用伺服器(generic servers,稱作GenServer)、
通用時間管理器以及事件處理器(又稱GenEvent)的API。
這些,連同“管理者”樹,都可以在Mix和OTP手冊裡找到詳細說明。
相關文章
- 11-程式碼塊和變數的作用域變數
- Shell程式設計-11-子Shell和Shell巢狀程式設計巢狀
- 11-建構函式函式
- 11-記憶體分析記憶體
- rust-algorithms:11-快速排序RustGo排序
- 11-自定義cell(2種方法)
- 【Objective-C】11-構造方法Object構造方法
- Java入門系列-11-類和物件Java物件
- Ext學習筆記11-佈局筆記
- 11-物件導向-2-三大特性物件
- Object C學習筆記11-陣列Object筆記陣列
- Python學習之路11-武裝飛船Python
- Go語言學習之路-11-方法與介面Go
- 11-自定義組合控制元件以及使用控制元件
- 實驗11-使用keras完成邏輯迴歸Keras邏輯迴歸
- 鴻蒙Flutter實戰:11-使用 Flutter SDK 3.22.0鴻蒙Flutter
- 線上商城專案11-商品列表頁的排序實現排序
- 11-專案進度管理(3/10 十大管理)
- python ubuntu dlib 人臉識別11-物體追蹤PythonUbuntu
- MVC驗證11-對複雜型別使用jQuery非同步驗證MVC型別jQuery非同步
- 1-陣列-11-二分查詢-LeetCode704陣列LeetCode
- Vue 框架-11-介紹src檔案流程及根元件app+HBuilder 配置Vue框架元件APPUI
- [ARKit]11-[譯]在ARKit中建立一個時空門App:準備開始APP
- 【資料結構&演算法】11-樹基礎&二叉樹遍歷資料結構演算法二叉樹
- 零基礎創作專業wordpress網站11-製作頁尾(Footer)網站
- Linux——程式建立、程式終止、程式等待、程式程式替換Linux
- 子程式、孤兒程式,殭屍程式, init程式
- 程式——父子程式共享
- linux系統程式設計之程式(一):程式與程式Linux程式設計
- Perl程式:殭屍程式和孤兒程式
- python併發程式設計之程式1(守護程式,程式鎖,程式佇列)Python程式設計佇列
- 小程式開發,小程式代理,小程式加盟,小程式創業創業
- 小程式5:FTP程式FTP
- iPhone程式本地程式支援iPhone
- 程式-程式-執行緒執行緒
- 程式猿與程式媛
- 智慧城市同城小程式程式 附帶前端程式前端
- linux系統程式設計之程式(三):程式複製fork,孤兒程式,殭屍程式Linux程式設計