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

多進(jìn)程

Elixir強(qiáng)大的并發(fā)來自其actor并發(fā)模型,簡而言之就是可以使用大量的進(jìn)程來實(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)建十萬個(gè)進(jìn)程是輕而易舉的事情届囚,甚至比普通語言創(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è)異步的查詢函數(shù)并執(zhí)行段多,可以發(fā)現(xiàn)每次執(zhí)行函數(shù)都馬上返回,而新創(chuàng)建的進(jìn)程將會在后臺運(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)行的递雀,因此并不會按照順序輸出結(jié)果柄延。每一個(gè)進(jìn)程都是獨(dú)立的,不同的進(jìn)程是不能讀取對方的數(shù)據(jù)的缀程,而進(jìn)程之間想要通信需要通過message搜吧。

進(jìn)程中的message

通常的語言中,使用多線程進(jìn)行并發(fā)杨凑,所有線程共享內(nèi)存數(shù)據(jù)滤奈。而elixir提供的是aotor的進(jìn)程模型。進(jìn)程通過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)程見是無法共享內(nèi)存的,因此消息發(fā)送的時(shí)候存在著深拷貝(deep-copied)伪嫁。

發(fā)送信息使用 send函數(shù)领炫,接受消息使用 receive do 結(jié)構(gòu)。 send提供兩個(gè)參數(shù)张咳,第一個(gè)是進(jìn)程的標(biāo)識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來對消息做實(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)用之后會返回發(fā)送的內(nèi)容砰奕。由于是自身給自身發(fā)消息,所以可以在當(dāng)前的進(jìn)程中調(diào)用receive結(jié)構(gòu)拉取自身的mailbox的message。如果模式匹配失敗军援,當(dāng)前的進(jìn)程會被block常空。可以設(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è)消息將會被放回進(jìn)程的mailbox之中蝗蛙,僅需讀取下一條消息蝇庭。總結(jié)receive的工作流如下:

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

進(jìn)程通信

通常send消息都是異步執(zhí)行的,進(jìn)程發(fā)送消息之后就返回偷俭,然后當(dāng)前進(jìn)程并不知道子進(jìn)程的執(zhí)行狀況浪讳。通常我們需要子進(jìn)程的執(zhí)行過程,然后子進(jìn)程將會send消息來反饋主進(jìn)程社搅。創(chuàng)建了子進(jìn)程將會返回進(jìn)程標(biāo)識pid驻债,基于pid和message可以進(jìn)行進(jìn)程間的通信。重寫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)程再把這些消息讀取出來

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"

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

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

receive結(jié)構(gòu)會將它mailbox放坏,模式匹配不成功的時(shí)候會block進(jìn)程咙咽。可是一旦mailbox中的messge消費(fèi)完了淤年,receive的監(jiān)聽也就結(jié)束了钧敞,進(jìn)程會結(jié)束。因此需要在message消費(fèi)完畢之后仍然運(yùn)行進(jìn)程麸粮。使用while結(jié)構(gòu)很容易實(shí)現(xiàn)這樣的程序邏輯溉苛,elixir沒有循環(huán),可是有遞歸弄诲。

下面以數(shù)據(jù)庫服務(wù)為例來做說明愚战。其基本結(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,給客戶端(主進(jìn)程齐遵,調(diào)用者)提供了一個(gè)啟動入口start函數(shù)寂玲。這個(gè)函數(shù)將會創(chuàng)建一個(gè)服務(wù)端進(jìn)程,用于監(jiān)聽客戶端的發(fā)送的消息梗摇,韓寒處理返回敢茁。有人可能有以為,start和loop都是模塊中的函數(shù)留美,分別運(yùn)行在不同進(jìn)程中彰檬。其實(shí)模塊和進(jìn)程本身沒有特別的關(guān)系,模塊就是函數(shù)的集合谎砾,這些函數(shù)可以運(yùn)行在進(jìn)程中逢倍,僅此而已。后期關(guān)于類似的實(shí)現(xiàn)景图,可以用到更高級的gen_server较雕。

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

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ù)端的查詢方法亮蒋,run_async為客戶端的查詢方法,run_async將查詢信息和自身的pid發(fā)給服務(wù)端妆毕,服務(wù)端匹配之后查詢處理慎玖,然后再給客戶端pid發(fā)送查詢結(jié)果〉颜常客戶端同樣也使用receive結(jié)構(gòu)pull查詢結(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)行測試

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)程客戶端馬上就返回并且監(jiān)聽服務(wù)端的返回趁怔,可以服務(wù)端異步長時(shí)間處理湿硝,不能馬上返回,客戶端超時(shí)斷開了润努。第二次調(diào)用get_result的時(shí)候关斜,此時(shí)服務(wù)端已經(jīng)處理完畢,并發(fā)送結(jié)果給客戶端的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)程來處理查詢請求痢畜,可是服務(wù)端進(jìn)程監(jiān)聽的是自己進(jìn)程的mailbos,消費(fèi)消息卻是順序的鳍侣。如果客戶端調(diào)用十個(gè)查詢請求裁着,服務(wù)端同樣需要執(zhí)行10秒。為了避免這樣的情況拱她,一個(gè)簡單的處理就是每一個(gè)請求實(shí)現(xiàn)一個(gè)服務(wù)端進(jìn)程,也就是服務(wù)端實(shí)現(xiàn)一個(gè)進(jìn)程池扔罪。面對大量的客戶端就能處理了秉沼。等等,你一定以為實(shí)現(xiàn)進(jìn)程池是一個(gè)夸張的做法矿酵,畢竟直覺上進(jìn)程的創(chuàng)建和銷毀十分耗資源唬复。感謝Erlang的并發(fā)模式,我們可以在Elixir中輕而易舉的創(chuàng)建大量的進(jìn)程全肮,這個(gè)進(jìn)程和操作系統(tǒng)進(jìn)程概念不一樣敞咧,它甚至比操作系統(tǒng)的線程還要輕量級。

下面演示進(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é)果中辜腺,并沒有超過十秒休建,而是很快就返回了結(jié)果。

狀態(tài)

設(shè)想一下评疗,如果需要跟數(shù)據(jù)庫服務(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)一直被保持雕拼,兩次請求服務(wù)纵东,都是同一個(gè)連接的狀態(tài)。實(shí)際的服務(wù)器環(huán)境中啥寇,往往狀態(tài)不是一層不變的篮迎。此時(shí)我們需要更新狀態(tài)男图。一個(gè)簡單的技巧就是在loop函數(shù)中更新狀態(tài)。

def loop(state) do
    new_state = receive do      # 捕捉新狀態(tài)
        msg1 -> ...
        msg2 -> ...
    end
    loop(new_state)             # 更新狀態(tài)
end 

下面實(shí)現(xiàn)一個(gè)計(jì)算器服務(wù)來闡明狀態(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ù)雜。不過技術(shù)手段都是一樣的岂傲,只需要在loop函數(shù)中操作狀態(tài)即可难裆。當(dāng)應(yīng)用的狀態(tài)變得復(fù)雜的時(shí)候,是非有必要對代碼進(jìn)行組織镊掖。服務(wù)端進(jìn)程的模塊可以剝離出來專注請求的處理乃戈。下面針對之前的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)用方式亩进,客戶端需要知道開啟的后臺進(jìn)程號症虑。如果這個(gè)過程隱藏在模塊中,豈不是更簡潔归薛。elixir提供了Process 模塊的register函數(shù)谍憔,實(shí)現(xiàn)了針對進(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"}]

可見主籍,后臺執(zhí)行任務(wù)的進(jìn)程习贫,相對客戶端被隱藏啦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末千元,一起剝皮案震驚了整個(gè)濱河市苫昌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幸海,老刑警劉巖祟身,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異物独,居然都是意外死亡月而,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門议纯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來父款,“玉大人,你說我怎么就攤上這事瞻凤『┰埽” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵阀参,是天一觀的道長肝集。 經(jīng)常有香客問我,道長蛛壳,這世上最難降的妖魔是什么杏瞻? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任所刀,我火速辦了婚禮,結(jié)果婚禮上捞挥,老公的妹妹穿的比我還像新娘浮创。我一直安慰自己,他們只是感情好砌函,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布舰始。 她就那樣靜靜地躺著踱承,像睡著了一般颠蕴。 火紅的嫁衣襯著肌膚如雪渐裸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天仍劈,我揣著相機(jī)與錄音厕倍,去河邊找鬼。 笑死贩疙,一個(gè)胖子當(dāng)著我的面吹牛讹弯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屋群,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坏挠!你這毒婦竟也來了芍躏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤降狠,失蹤者是張志新(化名)和其女友劉穎对竣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榜配,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡否纬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛋褥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片临燃。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖烙心,靈堂內(nèi)的尸體忽然破棺而出膜廊,到底是詐尸還是另有隱情,我是刑警寧澤淫茵,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布爪瓜,位于F島的核電站,受9級特大地震影響匙瘪,放射性物質(zhì)發(fā)生泄漏铆铆。R本人自食惡果不足惜蝶缀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望薄货。 院中可真熱鬧翁都,春花似錦、人聲如沸菲驴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赊瞬。三九已至先煎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巧涧,已是汗流浹背薯蝎。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谤绳,地道東北人占锯。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像缩筛,于是被迫代替她去往敵國和親消略。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理瞎抛,服務(wù)發(fā)現(xiàn)艺演,斷路器,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,504評論 25 707
  • Linux 進(jìn)程管理與程序開發(fā) 進(jìn)程是Linux事務(wù)管理的基本單元桐臊,所有的進(jìn)程均擁有自己獨(dú)立的處理環(huán)境和系統(tǒng)資源胎撤,...
    JamesPeng閱讀 2,449評論 1 14
  • Jianwei's blog 首頁 分類 關(guān)于 歸檔 標(biāo)簽 巧用Android多進(jìn)程,微信断凶,微博等主流App都在用...
    justCode_閱讀 5,900評論 1 23
  • ? 泉州后城文化街96號伤提,一道狹窄的厝門讓路人覺得毫不起眼∪纤福可是誰也沒想到肿男,毫不起眼的厝門里竟深藏著一座氣勢恢弘、...
    福茶之心閱讀 696評論 0 0