Elixir 簡(jiǎn)明筆記(十九) --- 多進(jìn)程

多進(jìn)程

Elixir強(qiáng)大的并發(fā)來(lái)自其actor并發(fā)模型,簡(jiǎn)而言之就是可以使用大量的進(jìn)程來(lái)實(shí)現(xiàn)并發(fā)。elixir中的進(jìn)程依托與erlang虛擬機(jī)的存在,這個(gè)進(jìn)程與操作系統(tǒng)的進(jìn)程不一樣,雖然他們可以像原生進(jìn)程一樣在處理器中運(yùn)行,但是他們比原生進(jìn)程輕,在普通機(jī)器創(chuàng)建十萬(wàn)個(gè)進(jìn)程是輕而易舉的事情,甚至比普通語(yǔ)言創(chuàng)建線程還要輕便。下面就看看elixir的多進(jìn)程是如何work。

使用erlang的timer模塊,模擬進(jìn)程中耗時(shí)的操作。定義一個(gè)匿名函數(shù),運(yùn)行函數(shù)之后,可以看到iex在兩秒之后才打印消息,連續(xù)調(diào)用5次,則一共耗時(shí)10秒:


iex(1)> run_query = fn query_def ->
...(1)>     :timer.sleep 2000
...(1)>     "#{query_def} result"
...(1)> end
#Function<6.54118792/1 in :erl_eval.expr/5>
iex(2)> run_query.("query 1")
"query 1 result"
iex(3)> 1..5 |> Enum.map(&(run_query.("query #{&1}")))
["query 1 result", "query 2 result", "query 3 result", "query 4 result",
 "query 5 result"]

創(chuàng)建進(jìn)程,Elixir創(chuàng)建進(jìn)程只需要使用spawn宏即可,sqwan/1 接受一個(gè)匿名函數(shù),創(chuàng)建另外一個(gè)新進(jìn)程。

spawn(
    fn ->
        expression_1
        ...
        expression_2
    end
)

修改上面的例子,使用spawn創(chuàng)建一個(gè)新進(jìn)程??梢钥吹?,執(zhí)行spawn之后,馬上返回了函數(shù)調(diào)用的結(jié)果,為新進(jìn)程新pid。兩秒后,新進(jìn)程執(zhí)行并打印內(nèi)容返回到iex中。

iex(4)> spawn(fn -> IO.puts run_query.("1") end )
#PID<0.65.0>
1 result

因此可以定義一個(gè)異步的查詢(xún)函數(shù)并執(zhí)行,可以發(fā)現(xiàn)每次執(zhí)行函數(shù)都馬上返回,而新創(chuàng)建的進(jìn)程將會(huì)在后臺(tái)運(yùn)行,并打印最終結(jié)果:

iex(5)> async_query = fn query_def ->
...(5)>     spawn(fn -> IO.puts run_query.(query_def) end) end
#Function<6.54118792/1 in :erl_eval.expr/5>
iex(6)> async_query.("query 1")
#PID<0.71.0>
query 1 result
iex(7)> 1..5 |> Enum.map(&(async_query.("query #{&1}")))
[#PID<0.73.0>, #PID<0.74.0>, #PID<0.75.0>, #PID<0.76.0>, #PID<0.77.0>]
query 1 result
query 2 result
query 3 result
query 4 result
query 5 result

Elixir中,所有代碼都運(yùn)行在一個(gè)在進(jìn)程中,iex也是運(yùn)行在進(jìn)程中,并且是shell中的主進(jìn)程。上面的例子中,進(jìn)程是并發(fā)運(yùn)行的,因此并不會(huì)按照順序輸出結(jié)果。每一個(gè)進(jìn)程都是獨(dú)立的,不同的進(jìn)程是不能讀取對(duì)方的數(shù)據(jù)的,而進(jìn)程之間想要通信需要通過(guò)message

進(jìn)程中的message

通常的語(yǔ)言中,使用多線程進(jìn)行并發(fā),所有線程共享內(nèi)存數(shù)據(jù)。而elixir提供的是aotor的進(jìn)程模型。進(jìn)程通過(guò)message同步數(shù)據(jù)。進(jìn)程A想要讓進(jìn)程B做點(diǎn)事情,需要A給B的mailbox發(fā)送異步消息,B進(jìn)程讀取mailbox的消息,解析后執(zhí)行。因?yàn)檫M(jìn)程見(jiàn)是無(wú)法共享內(nèi)存的,因此消息發(fā)送的時(shí)候存在著深拷貝(deep-copied)。

發(fā)送信息使用 send函數(shù),接受消息使用 receive do 結(jié)構(gòu)。 send提供兩個(gè)參數(shù),第一個(gè)是進(jìn)程的標(biāo)識(shí)pid,第二個(gè)是所需要發(fā)送的數(shù)據(jù)。receive的結(jié)構(gòu)如下

receive do
  pattern_1 -> do_something
  pattern_2 -> do_something_else
end

receive 結(jié)構(gòu)和 case 結(jié)構(gòu)十分相似,也支持mailbox的模式匹配。在iex中,self表示當(dāng)前的進(jìn)程,下面使用self來(lái)對(duì)消息做實(shí)驗(yàn):

iex(8)> send(self, "a message")
"a message"
iex(9)> receive do
...(9)>   message -> IO.puts message
...(9)> end
a message
:ok
iex(10)> send(self, {:message, 1})
{:message, 1}
iex(11)> receive do
...(11)>   {:message, id} -> IO.puts "received message #{id}"
...(11)> end
received message 1
:ok
iex(13)> receive do
...(13)>    {_, _, _} ->
...(13)>        IO.puts "received"
...(13)> end

send調(diào)用之后會(huì)返回發(fā)送的內(nèi)容。由于是自身給自身發(fā)消息,所以可以在當(dāng)前的進(jìn)程中調(diào)用receive結(jié)構(gòu)拉取自身的mailbox的message。如果模式匹配失敗,當(dāng)前的進(jìn)程會(huì)被block??梢栽O(shè)置一個(gè)after分支,當(dāng)匹配失敗之后,執(zhí)行after的內(nèi)容。

iex(2)> receive do
...(2)>   {_,_,_} -> 'nonthing'
...(2)> after 3000 -> "message not received"
...(2)> end
"message not received"
iex(3)>

receive結(jié)構(gòu)主要從當(dāng)前的mailbox中pull數(shù)據(jù)。如果當(dāng)前消息模式匹配失敗,這個(gè)消息將會(huì)被放回進(jìn)程的mailbox之中,僅需讀取下一條消息??偨Y(jié)receive的工作流如下:

  1. 從mailbox中讀取第一條消息。
  2. 嘗試使用 receive中模式和消息進(jìn)行模式匹配,從上到下依次進(jìn)行匹配。
  3. 匹配成功則執(zhí)行對(duì)應(yīng)分支的代碼。
  4. 如果模式匹配失敗,將消息放回原處,接著出現(xiàn)下一條消息。
  5. 如果mailbox的隊(duì)列沒(méi)有消息了,則等待下一個(gè)消息到達(dá),消息一到達(dá),則重復(fù)開(kāi)始第一步。
  6. 如果存在after語(yǔ)句,在等到消息未到到或匹配失敗之后,執(zhí)行after的代碼分支邏輯。

進(jìn)程通信

通常send消息都是異步執(zhí)行的,進(jìn)程發(fā)送消息之后就返回,然后當(dāng)前進(jìn)程并不知道子進(jìn)程的執(zhí)行狀況。通常我們需要子進(jìn)程的執(zhí)行過(guò)程,然后子進(jìn)程將會(huì)send消息來(lái)反饋主進(jìn)程。創(chuàng)建了子進(jìn)程將會(huì)返回進(jìn)程標(biāo)識(shí)pid,基于pid和message可以進(jìn)行進(jìn)程間的通信。重寫(xiě)async_query 函數(shù)并調(diào)用:

iex(1)> run_query = fn(query_def) ->
...(1)>           :timer.sleep(2000)
...(1)>           "#{query_def} result"
...(1)>         end
#Function<6.54118792/1 in :erl_eval.expr/5>
iex(3)> async_query = fn(query_def) ->
...(3)>     caller = self
...(3)>     spawn(fn query_def ->
...(3)>         send(caller, {:query_result, run_query.(query_def)})
...(3)>     end)
...(3)> end
iex(4)> Enum.each(1..5, &async_query.("query #{&1}"))
:ok

新創(chuàng)建的子進(jìn)程給主進(jìn)程的mailbox發(fā)送了消息,主進(jìn)程再把這些消息讀取出來(lái)

iex(7)> get_result = fn ->
...(7)>     receive do
...(7)>         {:query_result, result} -> result
...(7)>     end
...(7)> end
iex(10)> get_result.()
"query 1 result"
iex(11)> get_result.()
"query 2 result"
iex(12)> get_result.()
"query 3 result"

客戶(hù)端服務(wù)端狀態(tài)

通過(guò)message和receive可以實(shí)現(xiàn)進(jìn)程間的通信,通常把主進(jìn)程當(dāng)成客戶(hù)端進(jìn)程,新創(chuàng)建的進(jìn)程當(dāng)成服務(wù)端進(jìn)程。那么cs之間的進(jìn)程通信會(huì)涉及到一下?tīng)顟B(tài)(state)的操作。所謂的server process 是指一些長(zhǎng)時(shí)間監(jiān)聽(tīng)消息的進(jìn)程,就像服務(wù)器進(jìn)程一樣永遠(yuǎn)運(yùn)行,處于一個(gè)無(wú)限循環(huán)當(dāng)中,監(jiān)聽(tīng)客戶(hù)端的消息,處理消息。

receive結(jié)構(gòu)會(huì)將它mailbox,模式匹配不成功的時(shí)候會(huì)block進(jìn)程??墒且坏﹎ailbox中的messge消費(fèi)完了,receive的監(jiān)聽(tīng)也就結(jié)束了,進(jìn)程會(huì)結(jié)束。因此需要在message消費(fèi)完畢之后仍然運(yùn)行進(jìn)程。使用while結(jié)構(gòu)很容易實(shí)現(xiàn)這樣的程序邏輯,elixir沒(méi)有循環(huán),可是有遞歸。

下面以數(shù)據(jù)庫(kù)服務(wù)為例來(lái)做說(shuō)明。其基本結(jié)構(gòu)如下:

defmodule DatabaseServer do
    
    def start do
        spawn(&loop/0)
    end

    defp loop do
        receive do
            # pass
        end
        loop
    end

end

DatabaseServer模塊實(shí)現(xiàn)了一個(gè)服務(wù)器循環(huán)進(jìn)程loop,給客戶(hù)端(主進(jìn)程,調(diào)用者)提供了一個(gè)啟動(dòng)入口start函數(shù)。這個(gè)函數(shù)將會(huì)創(chuàng)建一個(gè)服務(wù)端進(jìn)程,用于監(jiān)聽(tīng)客戶(hù)端的發(fā)送的消息,韓寒處理返回。有人可能有以為,start和loop都是模塊中的函數(shù),分別運(yùn)行在不同進(jìn)程中。其實(shí)模塊和進(jìn)程本身沒(méi)有特別的關(guān)系,模塊就是函數(shù)的集合,這些函數(shù)可以運(yùn)行在進(jìn)程中,僅此而已。后期關(guān)于類(lèi)似的實(shí)現(xiàn),可以用到更高級(jí)的gen_server。

接下來(lái)實(shí)現(xiàn)loop中的邏輯,以及數(shù)據(jù)庫(kù)的服務(wù)端和客戶(hù)端的查詢(xún)方法。

defmodule DatabaseServer do
    
    def start do
        spawn(&loop/0)
    end


    def run_async(server_pid, query_def) do
        send(server_pid, {:run_query, self, query_def})
    end

    defp loop do
        receive do
            
            {:run_query, caller, query_def} -> send(caller, {:query_result, run_query(query_def)})
            
        end
        loop
    end

    defp run_query(query_def) do
        :timer.sleep(2000)
        "#{query_def} result"
    end
end

run_query 為服務(wù)端的查詢(xún)方法,run_async為客戶(hù)端的查詢(xún)方法,run_async將查詢(xún)信息和自身的pid發(fā)給服務(wù)端,服務(wù)端匹配之后查詢(xún)處理,然后再給客戶(hù)端pid發(fā)送查詢(xún)結(jié)果??蛻?hù)端同樣也使用receive結(jié)構(gòu)pull查詢(xún)結(jié)果:

defmodule DatabaseServer do
    
    def start do
        spawn(&loop/0)
    end

    def run_async(server_pid, query_def) do
        send(server_pid, {:run_query, self, query_def})
    end

    def get_result do
        
        receive do
            {:query_result, result} -> result
        after 5000 ->
            {:error, :timeout}
        end
    end

    defp loop do
        receive do
        
            {:run_query, caller, query_def} -> send(caller, {:query_result, run_query(query_def)})
        
        end
        loop
    end

    defp run_query(query_def) do
        :timer.sleep(2000)
        "#{query_def} result"
    end
end

運(yùn)行測(cè)試

iex(1)> server_pid = DatabaseServer.start
#PID<0.63.0>
iex(2)> DatabaseServer.run_async(server_pid, "query 1")
{:run_query, #PID<0.61.0>, "query 1"}
iex(3)> DatabaseServer.get_result
"query 1 result"
iex(4)> DatabaseServer.run_async(server_pid, "query 2")
{:run_query, #PID<0.61.0>, "query 2"}
iex(5)> DatabaseServer.get_result
"query 2 result"
iex(6)> DatabaseServer.get_result
{:error, :timeout}

把 timer.sleep 改成 10s后, 進(jìn)程客戶(hù)端馬上就返回并且監(jiān)聽(tīng)服務(wù)端的返回,可以服務(wù)端異步長(zhǎng)時(shí)間處理,不能馬上返回,客戶(hù)端超時(shí)斷開(kāi)了。第二次調(diào)用get_result的時(shí)候,此時(shí)服務(wù)端已經(jīng)處理完畢,并發(fā)送結(jié)果給客戶(hù)端的mailbox。

iex(2)> DatabaseServer.run_async(server_pid, "query 1")
{:run_query, #PID<0.61.0>, "query 1"}
iex(3)> DatabaseServer.get_result
{:error, :timeout}
iex(4)> DatabaseServer.get_result
"query 1 result"

服務(wù)端進(jìn)程都是順序的

盡管實(shí)現(xiàn)了服務(wù)端進(jìn)程來(lái)處理查詢(xún)請(qǐng)求,可是服務(wù)端進(jìn)程監(jiān)聽(tīng)的是自己進(jìn)程的mailbos,消費(fèi)消息卻是順序的。如果客戶(hù)端調(diào)用十個(gè)查詢(xún)請(qǐng)求,服務(wù)端同樣需要執(zhí)行10秒。為了避免這樣的情況,一個(gè)簡(jiǎn)單的處理就是每一個(gè)請(qǐng)求實(shí)現(xiàn)一個(gè)服務(wù)端進(jìn)程,也就是服務(wù)端實(shí)現(xiàn)一個(gè)進(jìn)程池。面對(duì)大量的客戶(hù)端就能處理了。等等,你一定以為實(shí)現(xiàn)進(jìn)程池是一個(gè)夸張的做法,畢竟直覺(jué)上進(jìn)程的創(chuàng)建和銷(xiāo)毀十分耗資源。感謝Erlang的并發(fā)模式,我們可以在Elixir中輕而易舉的創(chuàng)建大量的進(jìn)程,這個(gè)進(jìn)程和操作系統(tǒng)進(jìn)程概念不一樣,它甚至比操作系統(tǒng)的線程還要輕量級(jí)。

下面演示進(jìn)程池的用法:

iex(1)> pool = 1..100 |> Enum.map(fn _ -> DatabaseServer.start end)
[#PID<0.64.0>, #PID<0.65.0>, #PID<0.66.0>, #PID<0.67.0>, #PID<0.68.0>,
 #PID<0.69.0>, #PID<0.70.0>, #PID<0.71.0>, #PID<0.72.0>, #PID<0.73.0>,
 #PID<0.74.0>, #PID<0.75.0>, #PID<0.76.0>, #PID<0.77.0>, #PID<0.78.0>,
 #PID<0.79.0>, #PID<0.80.0>, #PID<0.81.0>, #PID<0.82.0>, #PID<0.83.0>,
 #PID<0.84.0>, #PID<0.85.0>, #PID<0.86.0>, #PID<0.87.0>, #PID<0.88.0>,
 #PID<0.89.0>, #PID<0.90.0>, #PID<0.91.0>, #PID<0.92.0>, #PID<0.93.0>,
 #PID<0.94.0>, #PID<0.95.0>, #PID<0.96.0>, #PID<0.97.0>, #PID<0.98.0>,
 #PID<0.99.0>, #PID<0.100.0>, #PID<0.101.0>, #PID<0.102.0>, #PID<0.103.0>,
 #PID<0.104.0>, #PID<0.105.0>, #PID<0.106.0>, #PID<0.107.0>, #PID<0.108.0>,
 #PID<0.109.0>, #PID<0.110.0>, #PID<0.111.0>, #PID<0.112.0>, #PID<0.113.0>, ...]
iex(2)> 1..5 |>
...(2)>     Enum.each(fn query_def ->
...(2)>         server_pid = Enum.at(pool, :random.uniform(100) - 1)
...(2)>         DatabaseServer.run_async(server_pid, query_def)
...(2)>     end)
:ok
iex(3)> 1..5 |>
...(3)>           Enum.map(fn(_) -> DatabaseServer.get_result end)
["3 result", "5 result", "4 result", "1 result", "2 result"]

運(yùn)行的結(jié)果中,并沒(méi)有超過(guò)十秒,而是很快就返回了結(jié)果。

狀態(tài)

設(shè)想一下,如果需要跟數(shù)據(jù)庫(kù)服務(wù)交互的時(shí)候,首先當(dāng)然是需要建立一個(gè)連接。連接就必須保持socket能夠正確的工作。因此也需要在進(jìn)程中保持狀態(tài),可以修改loop函數(shù)實(shí)現(xiàn)。

defmodule DatabaseServer do
    
    def start do
        spawn(fn ->
            connection = :random.uniform(1000)
            loop(connection)
        end)
    end

    def run_async(server_pid, query_def) do
        send(server_pid, {:run_query, self, query_def})
    end

    def get_result do
        receive do
            {:query_result, result} -> result
        after 5000 ->
            {:error, :timeout}
        end
    end

    defp loop(connection) do
        receive do  
            {:run_query, from_pid, query_def} -> 
                query_result = run_query(connection, query_def)
                send(from_pid, {:query_result, query_result})

        end
        loop(connection)
    end

    defp run_query(connection, query_def) do
        :timer.sleep(2000)
        "Connection #{connetion}: #{query_def} result"
    end
end

iex(1)> server_pid = DatabaseServer.start
#PID<0.63.0>
iex(2)> DatabaseServer.run_async(server_pid, "query 1")
{:run_query, #PID<0.61.0>, "query 1"}
iex(3)> DatabaseServer.get_result
"Connection 444: query 1 result"
iex(4)> DatabaseServer.run_async(server_pid, "query 2")
{:run_query, #PID<0.61.0>, "query 2"}
iex(5)> DatabaseServer.get_result
"Connection 444: query 2 result"

start 函數(shù)中創(chuàng)建了一些連接,然后loop中把這個(gè)狀態(tài)傳遞到進(jìn)程執(zhí)行代碼的地方。從iex的結(jié)果可以看出,這個(gè)狀態(tài)一直被保持,兩次請(qǐng)求服務(wù),都是同一個(gè)連接的狀態(tài)。實(shí)際的服務(wù)器環(huán)境中,往往狀態(tài)不是一層不變的。此時(shí)我們需要更新?tīng)顟B(tài)。一個(gè)簡(jiǎn)單的技巧就是在loop函數(shù)中更新?tīng)顟B(tài)。

def loop(state) do
    new_state = receive do      # 捕捉新?tīng)顟B(tài)
        msg1 -> ...
        msg2 -> ...
    end
    loop(new_state)             # 更新?tīng)顟B(tài)
end 

下面實(shí)現(xiàn)一個(gè)計(jì)算器服務(wù)來(lái)闡明狀態(tài)更新技巧。

defmodule Calculator do
    
    def start do
        spawn(fn ->loop(0) end)
    end

    def loop(current_value) do
        new_value = receive do
            {:value, caller} ->
                send(caller, {:response, current_value})
                current_value
            {:add, value} -> current_value + value
            {:sub, value} -> current_value - value
            {:mul, value} -> current_value * value
            {:div, value} -> current_value / value

            invalid_request ->
                IO.puts "invalid request #{inspect invalid_request}"
                current_value
        end
        loop(new_value)
    end

    def value(server_pid) do
        send(server_pid, {:value, self})
        receive do
            {:response, value} -> value
        end
    end

    def add(server_pid, value), do: send(server_pid, {:add, value})
    def sub(server_pid, value), do: send(server_pid, {:sub, value})
    def mul(server_pid, value), do: send(server_pid, {:mul, value})
    def div(server_pid, value), do: send(server_pid, {:div, value})

end

iex(1)> calculator_pid = Calculator.start
#PID<0.63.0>
iex(2)> Calculator.value(calculator_pid)
0
iex(3)> Calculator.add(calculator_pid, 10)
{:add, 10}
iex(4)> Calculator.sub(calculator_pid, 5)
{:sub, 5}
iex(5)> Calculator.mul(calculator_pid, 3)
{:mul, 3}
iex(6)> Calculator.div(calculator_pid, 5)
{:div, 5}
iex(7)> Calculator.value(calculator_pid)
3.0

通常情況下,狀態(tài)遠(yuǎn)遠(yuǎn)比一個(gè)數(shù)字復(fù)雜。不過(guò)技術(shù)手段都是一樣的,只需要在loop函數(shù)中操作狀態(tài)即可。當(dāng)應(yīng)用的狀態(tài)變得復(fù)雜的時(shí)候,是非有必要對(duì)代碼進(jìn)行組織。服務(wù)端進(jìn)程的模塊可以剝離出來(lái)專(zhuān)注請(qǐng)求的處理。下面針對(duì)之前的todo應(yīng)用,使用多進(jìn)程進(jìn)行改下一下:

defmodule TodoServer do
    
    def start do
        spawn(fn -> loop(TodoList.new) end)
    end

    defp loop(todo_list) do
        new_todo_list = receive do
            message -> process_message(todo_list, message)
        end
        loop(new_todo_list)
    end


    def process_message(todo_list, {:add_entry, new_entry}) do
        TodoList.add_entry(todo_list, new_entry)
    end

    def process_message(todo_list, {:entries, caller, date}) do
        send(caller, {:todo_entries, TodoList.entries(todo_list, date)})
        todo_list
    end

    def add_entry(todo_server, new_entry) do
        send(todo_server, {:add_entry, new_entry})
    end

    def entries(todo_server, date) do
        send(todo_server, {:entries, self, date})
        receive do
            {:todo_entries, entries} -> entries
        after 5000 ->
            {:error, :timeout}
        end
    end
end

調(diào)用方式如下:

iex(1)> todo_server = TodoServer.start
#PID<0.66.0>
iex(2)> TodoServer.add_entry(todo_server,
...(2)>                   %{date: {2013, 12, 19}, title: "Dentist"})
{:add_entry, %{date: {2013, 12, 19}, title: "Dentist"}}
iex(3)> TodoServer.entries(todo_server, {2013, 12, 19})
[%{date: {2013, 12, 19}, id: 1, title: "Dentist"}]
iex(8)> TodoServer.add_entry(todo_server,
...(8)>                   %{date: {2013, 12, 20}, title: "Shopping"})
{:add_entry, %{date: {2013, 12, 20}, title: "Shopping"}}
iex(9)> TodoServer.entries(todo_server, {2013, 12, 19})
[%{date: {2013, 12, 19}, id: 1, title: "Dentist"}]
iex(10)> TodoServer.entries(todo_server, {2013, 12, 20})
[%{date: {2013, 12, 20}, id: 2, title: "Shopping"}]
iex(11)> TodoServer.add_entry(todo_server,
...(11)>                   %{date: {2013, 12, 19}, title: "Movies"})
{:add_entry, %{date: {2013, 12, 19}, title: "Movies"}}
iex(12)> TodoServer.entries(todo_server, {2013, 12, 19})
[%{date: {2013, 12, 19}, id: 3, title: "Movies"},
 %{date: {2013, 12, 19}, id: 1, title: "Dentist"}]

這樣的調(diào)用方式,客戶(hù)端需要知道開(kāi)啟的后臺(tái)進(jìn)程號(hào)。如果這個(gè)過(guò)程隱藏在模塊中,豈不是更簡(jiǎn)潔。elixir提供了Process 模塊的register函數(shù),實(shí)現(xiàn)了針對(duì)進(jìn)程的設(shè)置別名的應(yīng)用。

其用法如下:

iex(1)> Process.register(self, :some_name)
iex(2)> send(:some_name, :msg)
iex(3)> receive do
          msg -> IO.puts "received #{msg}"
        end
received msg

修改TodoServer如下:

defmodule TodoServer do
    
    def start do
        pid = spawn(fn -> loop(TodoList.new) end)
        Process.register(pid, :todo_server)
    end

    defp loop(todo_list) do
        new_todo_list = receive do
            message -> process_message(todo_list, message)
        end
        loop(new_todo_list)
    end


    def process_message(todo_list, {:add_entry, new_entry}) do
        TodoList.add_entry(todo_list, new_entry)
    end

    def process_message(todo_list, {:entries, caller, date}) do
        send(caller, {:todo_entries, TodoList.entries(todo_list, date)})
        todo_list
    end

    def add_entry(new_entry) do
        send(:todo_server, {:add_entry, new_entry})
    end

    def entries(date) do
        send(:todo_server, {:entries, self, date})
        receive do
            {:todo_entries, entries} -> entries
        after 5000 ->
            {:error, :timeout}
        end
    end
end

調(diào)用方式如下:

iex(1)> TodoServer.start
true
iex(2)> TodoServer.add_entry(%{date: {2013, 12, 19}, title: "Dentist"})
{:add_entry, %{date: {2013, 12, 19}, title: "Dentist"}}
iex(3)> TodoServer.add_entry(%{date: {2013, 12, 20}, title: "Shopping"})
{:add_entry, %{date: {2013, 12, 20}, title: "Shopping"}}
iex(4)> TodoServer.add_entry(%{date: {2013, 12, 19}, title: "Movies"})
{:add_entry, %{date: {2013, 12, 19}, title: "Movies"}}
iex(5)> TodoServer.entries({2013, 12, 19})
[%{date: {2013, 12, 19}, id: 3, title: "Movies"},
 %{date: {2013, 12, 19}, id: 1, title: "Dentist"}]

可見(jiàn),后臺(tái)執(zhí)行任務(wù)的進(jìn)程,相對(duì)客戶(hù)端被隱藏啦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,591評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,113評(píng)論 25 709
  • Linux 進(jìn)程管理與程序開(kāi)發(fā) 進(jìn)程是Linux事務(wù)管理的基本單元,所有的進(jìn)程均擁有自己獨(dú)立的處理環(huán)境和系統(tǒng)資源,...
    JamesPeng閱讀 2,598評(píng)論 1 14
  • Jianwei's blog 首頁(yè) 分類(lèi) 關(guān)于 歸檔 標(biāo)簽 巧用Android多進(jìn)程,微信,微博等主流App都在用...
    justCode_閱讀 6,120評(píng)論 1 23
  • ? 泉州后城文化街96號(hào),一道狹窄的厝門(mén)讓路人覺(jué)得毫不起眼??墒钦l(shuí)也沒(méi)想到,毫不起眼的厝門(mén)里竟深藏著一座氣勢(shì)恢弘、...
    福茶之心閱讀 797評(píng)論 0 0

友情鏈接更多精彩內(nèi)容