2015-12-04 72 views
0

我對Erlang很陌生,試圖實現一個簡單的class,它有一些方法來模擬數據庫。 insert()只在流程圖中插入key -> valueretrieve()僅從地圖返回值。但是,我陷入了loop()。我究竟做錯了什麼?Erlang進程中的無限循環

-module(db). 
-export([start/0,stop/0,retrieve/1,insert/2]). 

start() -> 
    register(db, spawn(fun() -> 
       loop() 
      end) 
     ), 
    {started}. 


insert(Key, Value) -> 
    rpc({insert, Key, Value}). 

retrieve(Key) -> 
    rpc({retrieve, Key}). 

stop() -> 
    rpc({stop}). 

rpc(Request) -> 
    db ! {self(), Request}, 
    receive 
    {db, Reply} -> 
     Reply 
    end. 

loop() -> 
    receive 
    {rpc, {insert, Key, Value}} -> 
     put(Key, Value), 
     rpc ! {db, done}, 
     loop(); 
    {rpc, {retrieve, Key}} -> 
     Val = get(Key), 
     rpc ! {db, Val}, 
     loop(); 
    {rpc, {stop}} -> 
     exit(db,ok), 
     rpc ! {db, stopped}  
    end. 

所以,編譯後:

我首先調用db:start(). 然後試圖db:insert("A", 1).時,它就會被stucked。

謝謝

+2

第一個問題,在你走之前:Erlang *沒有類或方法*。一切都是功能。每個函數都會返回一個值。雖然有[*進程*,而不是*類*](http://stackoverflow.com/questions/32294367/erlang-process-vs-java-thread/32296577#32296577),你可以發送*消息* ,但他們如何迴應這些問題的方式與方法完全不同。 – zxq9

+0

謝謝。你是對的。我仍然不明白Erlang的一切工作原理 –

回答

5

的問題是loop/0功能。您使用rpc原子來模式匹配收到的消息({rpc, {insert, Key, Value}}),但是,正如您在rpc/1函數中所看到的那樣,您始終會將格式爲{self(), Request}的消息發送到db進程。

self()函數返回的格式<X.Y.Z>一個PID,這將永遠比不上對原子rpc

例如,假設你想使用的功能insert/2插入一些數據和self()將返回PID <0.36.0> 。當rpc/1發送消息時,在線db ! {self(), {insert, Key, Value}},loop/0將收到{<0.36.0>, {insert, Key, Value}}消息,這將永遠不會匹配{rpc, {insert, Key, Value}},因爲rpc是一個原子。

的解決辦法是改變rpc原子變量,像這樣:

loop() -> 
receive 
{Rpc, {insert, Key, Value}} -> 
    put(Key, Value), 
    Rpc ! {db, done}, 
    loop(); 
{Rpc, {retrieve, Key}} -> 
    Val = get(Key), 
    Rpc ! {db, Val}, 
    loop(); 
{Rpc, {stop}} -> 
    Rpc ! {db, stopped}, 
    exit(whereis(db),ok) 

end. 

二郎變量以大寫字母開頭,這是不是rpc爲什麼我用Rpc

P.S:其實,你還有其他兩個問題:

  1. loop/0,在那裏你處理stop消息的最後一部分,你叫exit(db, ok)之前,你居然回答rpc。在這種情況下,你永遠不會收到從db進程返回的{db, stopped}消息,該消息在那段時間已經死亡。這就是爲什麼我改變了訂單,在Rpc ! {db, stopped}之後撥打exit/2
  2. 當您撥打exit/2時,您通過了作爲第一個參數的db(原子),但exit/2函數期望將PID作爲第一個參數,這會引起badarg錯誤。這就是爲什麼我將它更改爲exit(whereis(db), ok)
+0

謝謝!就是這樣 –

+1

不客氣,@LuisLavieri。你應該閱讀zxq9的答案,這是指編碼併發erlang的一些重要問題。 爲了簡單起見,您還應該檢查OTP。由於你是Erlang的新手,我建議你[學習一些Erlang書籍](http://learnyousomeerlang.com/)。恕我直言,這是最好的開始。 –

2

讓我們仔細看看這一點。 「rpc」是什麼意思? 「遠程過程調用」 - 當然。但是在Erlang的一切是一個rpc,所以我們傾向於不使用這個術語。相反,我們區分同步消息(呼叫者阻止,等待響應)和異步消息(呼叫者只需觸發消息並在世界中無需關心的情況下運行)。我們傾向於使用術語「呼叫」來表示同步消息,而使用「施放」表示異步消息。

我們可以很容易地編寫,因爲調用看起來很像上面的rpc,在Erlang中增加了一個獨特的參考值來標記消息並監視我們發送消息的過程,以防萬一它崩潰(所以我們沒有得到左邊掛,等待永遠不會到來的迴應......我們將在一點觸摸你的代碼):

% Synchronous handler 
call(Proc, Request) -> 
    Ref = monitor(process, Proc), 
    Proc ! {self(), Ref, Request}, 
    receive 
     {Ref, Res} -> 
      demonitor(Ref, [flush]), 
      Res; 
     {'DOWN', Ref, process, Proc, Reason} -> 
      {fail, Reason} 
    after 1000 -> 
     demonitor(Ref, [flush]), 
     {fail, timeout} 
    end. 

Cast是更容易一點:

cast(Proc, Message) -> 
    Proc ! Message, 
    ok. 

以上調用的定義意味着我們發送給的進程將接收一條形式爲{SenderPID, Reference, Message}的消息。請注意,這是不同{sender, reference, message},因爲小寫字母值爲atoms,這意味着它們是它們自己的值。

當我們receive消息我們是匹配關於接收到的消息的形狀和值。這意味着,如果我有

receive 
    {number, X} -> 
     do_stuff(X) 
end 
在我的代碼

,它不會匹配過程坐在那receive得到一個消息{blah, 25}。如果它收到另一個消息{number, 26}那麼它將匹配,即receive將調用do_stuff/1並且該過程將繼續。 (這兩件事 - atomsVariables之間的區別與receive作品中的匹配方式 - 是您的代碼掛起的原因。)初始郵件{blah, 25}仍將位於郵箱中,但位於隊列的前端,所以下一個receive有機會匹配它。郵箱的這個屬性有時非常有用。

但是,什麼是一個全部看起來像?

高於你期望三種消息:

  • {insert, Key, Value}
  • {retrieve, Key}
  • stop

你穿起來有所不同,但是這是你想什麼營業結束去做。通過我在上面寫到的call/2函數運行插入消息將會看起來像這樣:{From, Ref, {insert, Key, Value}}。所以,如果我們期望從進程的接收循環中得到任何響應,我們需要按照確切的形式進行匹配。我們如何捕捉意外消息或形成不良的消息?在receive條款的最後,我們可以把一個赤裸裸的變量,以匹配任何其他

loop(State) -> 
    receive 
     {From, Ref, {insert, Key, Value}} -> 
      NewState = insert(Key, Value, State), 
      From ! {Ref, ok}, 
      loop(NewState); 
     {From, Ref, {retrieve, Key}} -> 
      Value = retrieve(Key, State), 
      From ! {Ref, {ok, Value}}, 
      loop(State); 
     {From, Ref, stop} -> 
      ok = io:format("~tp: ~tp told me to stop!~n", [self(), From]), 
      From ! {Ref, shutting_down}, 
      exit(normal) 
     Unexpected -> 
      ok = io:format("~tp: Received unexpected message: ~tp~n", 
          [self(), Unexpected]), 
      loop(State) 
    end. 

你會發現,我使用進程字典。不要使用過程字典。這不是它的目的。你會覆蓋重要的東西。或者丟掉重要的東西。或者......呃,不要這樣做。使用字典或映射或gb_tree或其他代替,並將其作爲過程'State變量傳遞。一旦你稍後開始編寫OTP代碼,這對你來說將變得非常自然。

玩弄這些東西,你很快就會高興地發送垃圾郵件到你的程序死亡。

+0

感謝您的詳細解釋。我絕對嘗試這一點。 –