elixir spwan以及spawn_link(:trap_exit)

對於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 
發佈了28 篇原創文章 · 獲贊 20 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章