多進(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的工作流如下:
- 從mailbox中讀取第一條消息捡硅。
- 嘗試使用 receive中模式和消息進(jìn)行模式匹配哮内,從上到下依次進(jìn)行匹配。
- 匹配成功則執(zhí)行對應(yīng)分支的代碼壮韭。
- 如果模式匹配失敗北发,將消息放回原處,接著出現(xiàn)下一條消息喷屋。
- 如果mailbox的隊(duì)列沒有消息了琳拨,則等待下一個(gè)消息到達(dá),消息一到達(dá)屯曹,則重復(fù)開始第一步狱庇。
- 如果存在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)程习贫,相對客戶端被隱藏啦。