檢視原始碼 程序
程序
Erlang 的設計目標是為了實現大規模並行。Erlang 程序是輕量級的(可動態增長和縮減),佔用記憶體小,創建和終止速度快,且排程開銷低。
程序創建
通過調用 spawn()
來創建程序。
spawn(Module, Name, Args) -> pid()
Module = Name = atom()
Args = [Arg1,...,ArgN]
ArgI = term()
spawn()
創建一個新的程序並返回其 pid。
新程序開始執行 Module:Name(Arg1,...,ArgN)
,其中的參數是(可能為空的)Args
參數列表的元素。
存在許多不同的 spawn
內建函數 (BIFs)
已註冊的程序
除了使用 pid 來尋址程序之外,還有一些 BIF 用於以名稱註冊程序。名稱必須是 atom,並且如果程序終止,則會自動取消註冊。
BIF | 描述 |
---|---|
register(Name, Pid) | 將名稱 Name (一個 atom)與程序 Pid 關聯。 |
registered/0 | 返回一個已使用 register/2 註冊的名稱列表。 |
whereis(Name) | 返回在 Name 下註冊的 pid,如果名稱未註冊,則返回 undefined 。 |
表格:名稱註冊 BIF
程序別名
當向程序發送訊息時,接收程序可以透過 Pid、已註冊的名稱或程序別名(類型為 reference 的 term)來識別。程序別名設計的典型使用案例是請求/回覆場景。在發送回覆時使用程序別名,使得回覆的接收者能夠在操作逾時或程序之間的連線遺失時,防止回覆到達其訊息佇列。
當使用 send 運算子 (!
) 或 send BIF(例如 erlang:send/2
)發送訊息時,可以使用程序別名作為接收者的識別符。只要程序別名處於啟用狀態,訊息的傳遞方式將與使用創建別名的程序的程序識別符相同。當別名已停用時,使用別名傳送的訊息將在進入接收者的訊息佇列之前被丟棄。請注意,在停用時已進入訊息佇列的訊息將不會被移除。
程序別名可以通過調用 alias/0,1
BIF 之一或同時創建別名和監視器來創建。如果別名與監視器一起創建,則相同的 reference 將同時用作監視器 reference 和別名。同時創建監視器和別名是通過將 {alias, _}
選項傳遞給 monitor/3
BIF 來完成的。{alias, _}
選項也可以在透過 spawn_opt()
或 spawn_request()
創建監視器時傳遞。
程序別名可以通過創建該別名的程序調用 unalias/1
BIF 來停用。也可以在特定事件時自動停用別名。請參閱 alias/1
BIF 的文件,以及 monitor/3
BIF 的 {alias, _}
選項,以取得更多關於自動停用別名的資訊。
不能執行以下操作
- 創建一個識別呼叫者以外的其他程序的別名。
- 停用除非識別呼叫者的別名。
- 查找別名。
- 查找別名識別的程序。
- 檢查別名是否處於啟用狀態。
- 檢查 reference 是否為別名。
這些都是與效能、可擴展性和分佈透明性相關的刻意設計決策。
程序終止
當程序終止時,它總是會以一個退出原因終止。原因可以是任何 term。
如果退出原因是 atom normal
,則稱該程序正常終止。沒有更多程式碼要執行的程序會正常終止。
當發生運行時錯誤時,程序會以退出原因 {Reason,Stack}
終止。請參閱 退出原因。
程序可以通過調用以下 BIF 之一來終止自身
然後,程序會以 exit/1
的原因 Reason
或其他的原因 {Reason,Stack}
終止。
如果程序收到一個退出訊號,其退出原因不是 normal
,程序也可能終止,請參閱 錯誤處理。
訊號
Erlang 程序和 Erlang 連接埠之間的所有通信都是通過發送和接收異步訊號來完成的。最常見的訊號是 Erlang 訊息訊號。可以使用 send 運算子 !
發送訊息訊號。接收程序可以使用 receive
運算式從訊息佇列中提取接收到的訊息。
同步通信可以分解為多個異步訊號。這種同步通信的一個例子是當第一個參數不等於調用程序的程序識別符時,對 erlang:process_info/2
BIF 的調用。調用者會發送一個請求訊息的異步訊號,然後阻塞等待包含請求訊息的回覆訊號。當請求訊號到達目的地時,目標程序會以請求的訊息回覆。
發送訊號
程序和連接埠使用許多訊號來通信。以下列表包含最重要的訊號。在所有請求/回覆訊號對的情況下,請求訊號由調用特定 BIF 的程序發送,當請求的操作已執行時,回覆訊號會發送回該程序。
message
- 當使用 send 運算子!
時,或當調用erlang:send/2,3
或erlang:send_nosuspend/2,3
BIF 之一時發送。link
- 當調用 link/1 BIF 時發送。unlink
- 當調用 unlink/1 BIF 時發送。exit
- 當通過調用 exit/2 BIF 顯式發送exit
訊號時,或當 連結程序終止時發送。如果訊號是因連結而發送的,則該訊號是在該程序使用的所有直接可見的 Erlang 資源被釋放後發送的。monitor
- 當調用 monitor/2,3 BIF 之一時發送。demonitor
- 當調用 demonitor/1,2 BIF 之一時,或當監視另一個程序的程序終止時發送。down
- 由 終止的被監視程序或連接埠發送。該訊號是在程序或連接埠使用的所有直接可見的 Erlang 資源被釋放後發送的。change
- 當 時間偏移 發生變更時,由本地執行時系統上的時鐘服務發送給已監視time_offset
的程序。group_leader
- 當調用 group_leader/2 BIF 時發送。spawn_request
/spawn_reply
,open_port_request
/open_port_reply
- 由於調用spawn/1,2,3,4
、spawn_link/1,2,3,4
、spawn_monitor/1,2,3,4
、spawn_opt/2,3,4,5
、spawn_request/1,2,3,4,5
或erlang:open_port/2
BIF 之一而發送。請求訊號將發送至spawn 服務,該服務會以回覆訊號回應。alive_request
/alive_reply
- 由於調用 is_process_alive/1 BIF 而發送。garbage_collect_request
/garbage_collect_reply
、check_process_code_request
/check_process_code_reply
、process_info_request
/process_info_reply
- 由於呼叫 garbage_collect/1,2、erlang:check_process_code/2,3 或 process_info/1,2 等 BIF 其中之一而傳送。請注意,如果請求是針對呼叫者本身,且為同步請求,則不會執行信號發送,呼叫者會在從 BIF 返回之前同步執行請求。port_command
、port_connect
、port_close
- 由進程使用 send 運算符 (!
),或呼叫send()
BIF 其中之一,傳送至本機節點上的埠。透過傳遞格式為{Owner, {command, Data}}
、{Owner, {connect, Pid}}
或{Owner, close}
的 term 作為訊息來發送信號。port_command_request
/port_command_reply
、port_connect_request
/port_connect_reply
、port_close_request
/port_close_reply
、port_control_request
/port_control_reply
、port_call_request
/port_call_reply
、port_info_request
/port_info_reply
- 由於呼叫erlang:port_command/2,3
、erlang:port_connect/2
、erlang:port_close/1
、erlang:port_control/3
、erlang:port_call/3
或erlang:port_info/1,2
等 BIF 其中之一而傳送。請求信號會傳送至本機節點上的埠,該埠會回應回覆信號。register_name_request
/register_name_reply
、unregister_name_request
/unregister_name_reply
、whereis_name_request
/whereis_name_reply
- 由於呼叫register/2
、unregister/1
或whereis/1
等 BIF 其中之一而傳送。請求信號會傳送至名稱服務,該服務會回應回覆信號。timer_start_request
/timer_start_reply
、timer_cancel_request
/timer_cancel_reply
- 由於呼叫erlang:send_after/3,4
、erlang:start_timer/3,4
或erlang:cancel_timer/1,2
等 BIF 其中之一而傳送。請求信號會傳送至 計時器服務,該服務會回應回覆信號。
前述提及的時鐘服務、名稱服務、計時器服務和生成服務是由執行階段系統提供的服務。這些服務中的每一個都由多個獨立執行的實體組成。此類服務可以被視為一組進程,實際上也可以這樣實作。由於每個服務都由多個獨立執行的實體組成,因此從一個服務傳送到一個進程的多個信號之間的順序並**不**會被保留。請注意,這並**不**違反該語言的信號排序保證。
先前描述的信號的實現可能會在執行階段發生變化,也可能由於實現上的變更而發生變化。您可以使用 receive
追蹤或檢查訊息佇列來偵測此類變更。然而,這些都是執行階段系統的內部實作細節,您**不**應該依賴它們。例如,許多回覆信號都是普通的訊息信號。當操作為同步時,回覆信號不必是訊息信號。目前的實作利用了這一點,並且根據系統的狀態,會使用其他方式來傳遞回覆信號。這些回覆信號的實作也可能隨時變更為不使用先前使用的訊息信號。
接收信號
信號會非同步且自動地被接收。進程無需執行任何操作來處理信號的接收,也無法執行任何操作來阻止它。特別是,信號接收並**不**與 receive
表達式的執行綁定,但可能發生在進程執行流程中的任何位置。
當進程收到信號時,會採取某種動作。採取的具體動作取決於信號類型、信號內容以及接收進程的狀態。針對最常見的信號所採取的動作
message
- 如果訊息信號是使用不再活動的進程別名傳送的,則訊息信號將被捨棄;否則,如果別名仍然處於活動狀態,或者訊息信號是由其他方式傳送的,則訊息會被添加到訊息佇列的末尾。將訊息新增至訊息佇列後,接收進程可以使用receive
表達式從訊息佇列中取得訊息。link
、unlink
- 非常簡化地來看,可以將其視為更新有關連結的進程本機資訊。連結協定的詳細描述可以在 _ERTS 使用者指南_ 的 _分配協定_ 章節中找到。exit
- 將接收器設定為退出狀態、捨棄信號,或將信號轉換為訊息並將其新增至訊息佇列的末尾。如果接收器被設定為退出狀態,則不會再執行任何 Erlang 程式碼,並且會排定該進程以進行終止。下面的 _接收退出信號_ 章節提供了有關接收到exit
信號時所採取的動作的更多詳細資訊。monitor
、demonitor
- 更新有關監視器的進程本機資訊。down
、change
- 如果對應的監視器仍然處於活動狀態,則轉換為訊息;否則,捨棄該信號。如果信號被轉換為訊息,則也會被新增至訊息佇列的末尾。group_leader
- 變更進程的群組領導者。spawn_reply
- 根據回覆以及spawn_request
信號的設定方式,轉換為訊息或捨棄信號。如果信號被轉換為訊息,則也會被新增至訊息佇列的末尾。如需更多資訊,請參閱spawn_request()
BIF。alive_request
- 排定執行 _是否活動_ 測試。如果進程處於退出狀態,則在釋放該進程使用的所有_直接可見的 Erlang 資源_ 之前,不會執行 _是否活動_ 測試。alive_reply
將在 _是否活動_ 測試執行後發送。process_info_request
、garbage_collect_request
、check_process_code_request
- 排定執行請求的操作。當操作執行完成後,將發送回覆信號。
請注意,接收到信號時所採取的一些動作涉及 _排定_ 進一步的動作,這些動作完成時將導致回覆信號。這表示回覆信號的發送順序可能與觸發這些操作的傳入信號的順序不同。然而,這並**不**違反該語言的信號排序保證。
進程訊息佇列中的訊息順序反映了自所有將訊息新增至訊息佇列的信號都會將訊息新增至訊息佇列的末尾以來,接收到與訊息對應的信號的順序。由於該語言的信號排序保證,來自同一傳送者的信號所對應的訊息也會按照信號的傳送順序排序。
直接可見的 Erlang 資源
如前所述,由於連結而導致的 exit
信號、down
信號以及來自退出進程的由於 alive_request
所產生的回覆信號,都會在終止進程所持有的所有 _直接可見的 Erlang 資源_ 被釋放後才會發送。此處所指的 _直接可見的 Erlang 資源_ 指的是該語言提供的所有資源,但不包含堆積資料所持有的資源、不乾淨的原生程式碼執行以及終止進程的進程識別碼。_直接可見的 Erlang 資源_ 的範例包括已註冊的名稱和ETS 表格。
排除的資源
在與該進程相關的所有內容都被釋放之前,該進程的進程識別碼無法被釋放以供重複使用。
進程在接收到退出信號時,如果正在 NIF 中執行不乾淨的原生程式碼,即使它仍在執行不乾淨的原生程式碼,也會被設定為退出狀態。_直接可見的 Erlang 資源_ 將被釋放,但執行階段系統無法強制原生程式碼停止執行。執行階段系統會嘗試防止不乾淨的原生程式碼的執行影響其他進程,例如,透過停用從已終止進程使用時的 enif_send()
等功能,但是如果 NIF 行為不當,它仍然會影響其他進程。行為良好的不乾淨 NIF 應測試其正在執行的進程是否已退出,如果已退出,則停止執行。
一般情況下,在所有需要發送的信號都已發送之前,程序的堆積(heap)不能被移除。堆積數據所持有的資源不僅包含堆積所在的記憶體區塊,還包括從堆積中參照的非堆積二進制檔案,以及透過 NIF 資源物件在堆積中持有的資源。
信號的傳遞
信號發送的時間點與信號到達目的地之間經過的時間長度是不確定的,但一定是正值。如果接收方已終止,則信號不會到達,但可能會觸發另一個信號。例如,傳送給不存在的進程的 link
信號會觸發一個 exit
信號,該信號會發回 link
信號的發起者。當透過分散式系統進行通訊時,如果分散式通道發生故障,信號可能會遺失。
唯一提供的信號排序保證如下:如果一個實體向同一個目標實體發送多個信號,則順序會被保留;也就是說,如果 A
向 B
發送信號 S1
,然後稍後向 B
發送信號 S2
,則保證 S1
不會在 S2
之後到達。請注意,S1
可能已經遺失,也可能沒有遺失。
不規則性
同步錯誤檢查 - 某些發送信號的功能,在節點上本地發送時,會進行同步錯誤檢查,如果接收方在發送信號時不存在,則會失敗。
- 當接收方是由預期在本地註冊的名稱識別時,發送運算符 (
!
)、erlang:send/2,3
BIF 以及erlang:send_nosuspend/2,3
BIF。 erlang:link/1
erlang:group_leader/2
- 當接收方是由預期在本地註冊的名稱識別時,發送運算符 (
Exit 信號的非預期行為 - 當一個進程透過呼叫
erlang:exit(self(), normal)
向自己發送帶有結束原因normal
的 exit 信號時,它將在收到exit
信號時終止。在所有其他情況下,當收到帶有結束原因normal
的 exit 信號時,該信號將被丟棄。當
收到帶有結束原因
時,採取的動作會因信號是因連結的進程終止而發送,還是使用kill
的 exit 信號exit/2
BIF 明確發送而有所不同。當使用exit/2
BIF 發送時,該信號不能被捕獲,但如果是因連結而發送的信號,則可以被捕獲。透過分散式系統進行阻斷式信號發送當透過分散式通道發送信號時,即使信號應該是非同步發送的,發送進程也可能會被暫停。這是由於通道內建的流量控制機制,該機制或多或少一直存在。當通道的輸出緩衝區大小達到分散式緩衝區忙碌限制時,在通道上發送的進程將被暫停,直到緩衝區的大小縮小到低於該限制為止。
根據緩衝區已滿的原因,暫停的進程恢復的時間可能會非常大。例如,這可能導致對 erpc:call() 的呼叫逾時時間顯著延遲。
由於此功能已存在很長時間,因此無法刪除它,但是可以使用
process_flag(async_dist, Bool)
在每個進程級別啟用完全非同步的分散式信號發送,這可用於解決因阻斷式信號發送而發生的問題。但是,請注意,您需要確保實作使用完全非同步分散式信號發送所傳送資料的流量控制,或確保此類資料的量已知始終受到限制;否則,您可能會陷入過度使用記憶體的情況。分散式緩衝區忙碌限制的大小可以透過呼叫
erlang:system_info(dist_buf_busy_limit)
來檢查。
前面提到這些不規則性無法修正,因為它們已成為 Erlang 的一部分太久,而且會破壞許多現有程式碼。
連結
兩個進程可以彼此連結。同樣,位於同一節點上的進程和連接埠也可以彼此連結。如果其中一個進程呼叫 link/1
BIF 並將另一個進程的進程識別符號作為參數,則可以在兩個進程之間建立連結。也可以使用以下其中一個 spawn BIF spawn_link()
、spawn_opt()
或 spawn_request()
建立連結。在這些情況下,spawn 操作和 link 操作將以原子方式執行。
如果連結的其中一方終止,則會向另一方發送 exit 信號。exit 信號將包含終止方的結束原因。
可以透過呼叫 unlink/1
BIF 來移除連結。
連結是雙向的,兩個進程之間只能有一個連結。重複呼叫 link()
沒有效果。任何一個相關的進程都可以建立或移除連結。
連結用於監控其他進程的行為,請參閱錯誤處理。
錯誤處理
Erlang 具有進程之間進行錯誤處理的內建功能。終止的進程會向所有連結的進程發出 exit 信號,這些進程也可以終止或以某種方式處理 exit。此功能可用於建立階層式程式結構,其中某些進程監視其他進程,例如,如果這些進程異常終止,則重新啟動它們。
有關使用此功能的 OTP 監督樹的更多資訊,請參閱 OTP 設計原則。
發送 Exit 信號
當進程或連接埠終止時,它將向所有與之連結的進程和連接埠發送 exit 信號。exit 信號將包含以下資訊:
發送者識別符號 - 終止的進程或連接埠的進程或連接埠識別符號。
接收者識別符號 - exit 信號傳送到的進程或連接埠的進程或連接埠識別符號。
link
旗標 - 將設定此旗標,表示 exit 信號是因連結而發送的。noproc
,如果先前呼叫link(PidOrPort)
BIF 時在設定連結時未找到進程或連接埠。識別為 exit 信號發送者的進程或連接埠將等於傳遞給link/1
的PidOrPort
引數。noconnection
,如果連結的進程位於不同的節點上,且節點之間的連線遺失或無法建立。在這種情況下,識別為 exit 信號發送者的進程或連接埠可能仍然處於活動狀態。
也可以透過呼叫 exit(PidOrPort, Reason)
BIF 明確發送 Exit 信號。Exit 信號會傳送給由 PidOrPort
引數識別的進程或連接埠。所傳送的 exit 信號將包含以下資訊:
發送者識別符號 - 呼叫
exit/2
的進程的進程識別符號。接收者識別符號 - exit 信號傳送到的進程或連接埠的進程或連接埠識別符號。
link
旗標 - 將不會設定此旗標,表示此 exit 信號不是因連結而發送的。結束原因 - 在呼叫
exit/2
時傳遞的項目作為Reason
。如果Reason
是原子kill
,接收方無法捕獲 exit 信號,且會在收到信號時無條件終止。
接收 Exit 信號
當進程接收到 exit 信號時會發生什麼情況,取決於
- 接收方在收到 exit 信號時的捕獲 exit 狀態。
- exit 信號的結束原因。
- exit 信號的發送者。
- exit 信號的
link
旗標的狀態。如果設定link
旗標,則 exit 信號是因連結而發送的;否則,exit 信號是由呼叫exit/2
BIF 發送的。 - 如果設定
link
旗標,則當收到 exit 信號時,發生的情況也取決於連結是否仍然處於活動狀態。
根據上述狀態,當進程收到 exit 信號時,將會發生以下情況:
如果出現以下情況,則會靜默丟棄 exit 信號
- 設定 exit 信號的
link
旗標,且對應的連結已停用。 - exit 信號的結束原因是原子
normal
,接收方未捕獲 exit,且接收方和發送者不是同一個進程。
- 設定 exit 信號的
如果出現以下情況,則接收進程會終止:
- 未設定 exit 信號的
link
旗標,且 exit 信號的結束原因是原子kill
。接收進程將以原子killed
作為結束原因而終止。 - 接收端未捕獲退出訊號,且退出原因不是原子
normal
。此外,如果退出訊號的link
旗標已設定,連結也必須處於活動狀態,否則退出訊號將會被丟棄。接收程序的退出原因將會等於退出訊號的退出原因。請注意,如果link
旗標已設定,則退出原因為kill
將不會轉換為killed
。 - 退出訊號的退出原因是原子
normal
,且退出訊號的發送者與接收者是同一個程序。在這種情況下,link
旗標不能被設定。接收程序的退出原因將會是原子normal
。
- 未設定 exit 信號的
如果接收端正在捕獲退出訊號,且退出訊號的
link
旗標- 未設定,並且訊號的退出原因不是原子
kill
,則退出訊號將會轉換為訊息訊號並添加到接收端訊息佇列的末尾。 - 已設定,並且對應的連結處於活動狀態。請注意,在這種情況下,退出原因為
kill
將不會終止該程序,並且也不會轉換為killed
。
轉換後的訊息格式將為
{'EXIT', SenderID, Reason}
,其中Reason
等於退出訊號的退出原因,而SenderID
是發送退出訊號的程序或埠的識別符。- 未設定,並且訊號的退出原因不是原子
監控器
連結的替代方案是監控器。程序 Pid1
可以透過呼叫 BIF erlang:monitor(process, Pid2)
為 Pid2
建立監控器。該函數會回傳一個參考值 Ref
。
如果 Pid2
以退出原因 Reason
終止,則會向 Pid1
發送一個 'DOWN' 訊息。
{'DOWN', Ref, process, Pid2, Reason}
如果 Pid2
不存在,則會立即發送 'DOWN' 訊息,且 Reason
會設定為 noproc
。
監控器是單向的。重複呼叫 erlang:monitor(process, Pid)
會建立多個獨立的監控器,並且每個監控器都會在 Pid
終止時發送 'DOWN' 訊息。
可以透過呼叫 erlang:demonitor(Ref)
來移除監控器。
可以為具有註冊名稱的程序建立監控器,也可以在其他節點上建立。
程序字典
每個程序都有自己的程序字典,可以透過呼叫以下 BIF 來存取