對於elixir來說Process是非常基礎的任務執行單元,以前對於spawn和spawn_link的理解不深,只知道後者是將子進程跟父進程鏈接,子進程意外崩潰也會導致父進程崩潰。今天剛好看了一片關於erlang的exit_trap進程標誌的介紹纔對這兩個概念做了更深入的瞭解
對於產生一個新進程去執行耗時操作:
1.我不關心子進程是否掛掉,選用spawn
2.我希望子進程掛掉,父進程進程也停掉,選用spawn_link
3.我希望子進程掛掉後能通知父進程(發送消息, 進程之間的通信使用消息機制,可以避免多線程共享內存帶來的死鎖問題)Process.flag(:trap_exit, true), spawn_link
#some logic here
#need to do some other things that is time-consuming
spawn fn ->
#do some stuff here
nil
end
spawn_link fn ->
#do some stuff here
nil
end
Process.flag(:trap_exit, true)
spawn_link fn ->
#do some stuff here
nil
end
下面寫一個列子來看一下這三種方式有什麼不同
defmodule Process.Test do
def test1 do
spawn fn ->
IO.puts "I'm a new process and i will crash"
Process.sleep 2000
exit(:abnormal)
end
end
def test2 do
spawn_link fn ->
IO.puts "I'm a new process and i will crash"
Process.sleep 2000
exit(:abnormal)
end
end
def test3 do
Process.flag(:trap_exit, true)
spawn_link fn ->
IO.puts "I'm a new process and i will crash"
Process.sleep 2000
exit(:abnormal)
end
end
end
以上代碼定義了一個module包含三個函數分別表示三種產生新進程的方法
打開一個iex(筆者使用環境是linux)
Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule Process.Test do
...(1)> def test1 do
...(1)> spawn fn ->
...(1)> IO.puts "I'm a new process and i will crash"
...(1)> Process.sleep 2000
...(1)> exit(:abnormal)
...(1)> end
...(1)> end
...(1)>
...(1)> def test2 do
...(1)> spawn_link fn ->
...(1)> IO.puts "I'm a new process and i will crash"
...(1)> Process.sleep 2000
...(1)> exit(:abnormal)
...(1)> end
...(1)> end
...(1)>
...(1)> def test3 do
...(1)> Process.flag(:trap_exit, true)
...(1)> spawn_link fn ->
...(1)> IO.puts "I'm a new process and i will crash"
...(1)> Process.sleep 2000
...(1)> exit(:abnormal)
...(1)> end
...(1)> end
...(1)> end
{:module, Process.Test,
<<70, 79, 82, 49, 0, 0, 8, 124, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 186,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:test3, 0}}
iex(2)>
下面來挨個執行module中的函數,首先是函數test1
iex(3)> Process.Test.test1
I'm a new process and i will crash
#PID<0.113.0>
我們能看到除了打印的信息外好像什麼也沒發生,其實子進程已經被exit函數非正常退出了,但我們沒看到一點出錯消息,其主要原因是使用了spawn,當前進程是不管子進程的運行狀態的。好,再執行下一個
iex(4)> Process.Test.test2
I'm a new process and i will crash
#PID<0.115.0>
** (EXIT from #PID<0.80.0>) :abnormal
Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
這次好像能看到** (EXIT from #PID<0.80.0>) :abnormal的信息了同時我們也能看到iex重新打印了啓動信息iex序號也從4變爲了1。代表子進程的異常退出確實也結束了父進程的正常執行
iex(1)> Process.Test.test3
I'm a new process and i will crash
#PID<0.119.0>
在當前繼續執行test3函數時,卻出現了和test1同樣的結果。這就是進程標誌:trap_exit在起作用了。spawn_link沒有使父進程也退出。但是好像還有一個問題:子進程異常退出的消息呢?
其實這裏父進程已經接收了消息,但是並沒有任何handler處理機制(沒用打印)我們可以稍微改一下程序,在test3中加入receive函數
defmodule Process.Test do
def test3 do
Process.flag(:trap_exit, true)
spawn_link fn ->
IO.puts "I'm a new process and i will crash"
Process.sleep 2000
exit(:abnormal)
end
receive do
msg ->
IO.inspect msg
_ ->
nil
end
end
end
將代碼拷入剛剛的iex中(這裏雖然是同名模塊,但是不用關閉之前的iex而是直接拷貝粘貼,elixir熱更新支持repl中直接替換代碼模塊)
{:module, Process.Test,
<<70, 79, 82, 49, 0, 0, 8, 228, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 186,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:test3, 0}}
iex(2)>
再次運行test3函數
iex(3)> Process.Test.test3
I'm a new process and i will crash
{:EXIT, #PID<0.120.0>, :abnormal}
{:EXIT, #PID<0.120.0>, :abnormal}
這時我們可以清楚的看到子進程的異常退出了(:abnormal)
下面簡介一下應用場景
假設服務器需要並行接受來自客戶端的請求,併爲每個請求提供服務。此時如果該服務我們需要關心他是否能順利完成則需要使用spawn_link或者使用Process.monitor監聽該應用服務進程,對於非正常退出的子應用服務,當我們接受到消息時可以選擇相應措施(重啓),這裏例程中使用的receive是阻塞式的,只爲了作爲演示。實際項目中可以使用GenServer的handle_info來接收並處理該消息,至於進程退出的消息結構如果使用Process.monitor pid的方式,每個pid進程結束時{:DOWN, ref, :process, _pid, reason}都會被髮送到調用monitor監聽的父進程則可以使用handle_info去處理該消息
defmodule Test do
use GenServer
def monitor_me pid do
Process.monitor pid
#這裏可以將該pid存入ets表中在handle_info接收到消息時可以匹配該pid做相應處理
end
def handle_info({:DOWN, ref, :process, _pid, reason}, state) do
#do something here when receive this message
{:noreply, state}
end
end