2010-12-06 137 views
2

以下代碼片段是由Francesco Cesarini和Simon Thompson,Erlang Programming撰寫的第112頁,作爲Erlang可能的競爭條件的一個例證。避免競爭條件

start() -> 
    case whereis(db_server) of 
    undefined -> 
     Pid = spawn(db_server, init, []), 
     register(db_server, Pid), 
     {ok, Pid}; 
    Pid when is_pid(Pid) -> 
     {error, already_started} 
    end. 

沒有逐字抄襲細節,作者解釋說,如果兩個進程同時執行的start(),然後處理1運行「不確定」部分,可能無法完成,因爲過程2使其被搶佔。過程2然後會運行「未定義」部分來完成。現在,當進程1恢復時,進程2已經註冊了db_server,導致其調用register()以引發運行時錯誤。我希望你能理解我的意思,因爲我不想把這本書的文本去掉。

我的問題是如何編碼上述確切功能以避免當兩個進程同時執行start()時潛在的競爭條件?

回答

1

你想啓動多少臺服務器?你原來的問題意味着一個,而對@cthulahoops的評論說,兩個,一個服務器和一個備份。對於兩臺服務器,你可以嘗試這樣的:

start() -> 
    case whereis(db_server) of 
     undefined -> 
      Spid = spawn(db_server, init, []), 
      %% In race condition there can be only one that succeeds to register 
      case catch register(db_server, Spid) of 
       true -> {ok,Spid};    %We are it 
       {error,_} ->     %Server registered, register as backup 
        register(db_server_backup, Spid), 
        {ok,Spid} 
      end; 
     _ ->         %Server registered, start backup 
      Bpid = spawn(db_server, init, []), 
      register(db_server_backup, Bpid), 
      {ok,Bpid} 
    end. 

雖然我還沒有運行它。

2

您可以使用gen_server來序列化請求。

+0

我知道OTP爲這些類型的問題提供強大的解決方案,但我對非OTP「設計模式」感興趣。感謝您的迴應。 – Max 2010-12-06 16:31:41

9

通常這可以通過讓生成的進程註冊自己的名字來解決,然後向父節點發回一個響應告訴父節點是否成功。

start() -> 
    Pid = spawn(db_server, init, [self()]), 
    receive {Pid, StartResult} -> 
     StartResult 
    end. 

init(Parent) -> 
    try register(db_server, self()) of 
     true -> 
      Parent ! {ok, started}, 
      real_init() 
    catch error:_ -> 
     Parent ! {error, already_started} 
    end. 

(可能無法編譯或工作。鍵入這裏沒有檢查。:))

你可以找到一個gen.erl這個認真落實版本。實際上,在真實代碼中,您只需使用OTP行爲來重用該版本。

+0

對我來說,問題是我想在問題中使用start/0,而不是像你所建議的那樣啓動/ 1。例如,我想調用兩次start/0,一次產生db_server,一次產生db_backup_server,如果db_server已經註冊。感謝您的迴應。 – Max 2010-12-06 16:35:10

+0

我只是讓代碼稍微比你的通用。編輯只有一個開始/ 0。 – cthulahoops 2010-12-06 16:56:37