2010-12-06 16 views
2

次のコードスニペットはErlangの可能な競合状態の例としてFrancesco CesariniとSimon Thompson、Erlang Programmingによる本の第112ページに記載されています。逐語的な詳細をコピーせずに競合状態の回避

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. 

は、著者らは2つのプロセスが同時にスタートを()を実行した場合、プロセス2は、それが先取りされますので、その後、可能性があります完了していない「未定義」セクションを実行している1を処理することを説明します。プロセス2は、「未定義」セクションを完了まで実行する。プロセス1が再開すると、db_serverはすでにプロセス2によって登録されているため、register()への呼び出しによって実行時エラーが発生します。私はあなたが本のテキストをはがしたくないので、私が何を意味するのかを理解することを願っています。

私の質問は、2つのプロセスが同時にstart()を実行すると、潜在的な競合状態を避けるために、上記の正確な機能をコード化する方法はありますか?

答えて

1

開始するサーバーの数はいくつですか? @cthulahoopsのコメントには2つのサーバーとバックアップが含まれていますが、オリジナルの質問には1つの意味が含まれています。私もそれを実行していない

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台のサーバーでは、あなたのような何かを試みることができます。

+0

2台のサーバーを起動します。私はもともと、O'Reillyの本の例を使っていました。なぜなら、人々が理解しやすく、それで私の質問に答える方が簡単だと思ったからです。フェールオーバーを提供するために、正確に2台のサーバー(プライマリとバックアップ)を起動したいと考えています。私はあなたの提案に似たコードを持っていますが、おそらくあなたのレースの状況を避ける方が良いでしょう。 – Max

2

gen_serverを使用して要求をシリアル化することができます。

+0

私はOTPがこれらのタイプの問題に対する堅牢なソリューションを提供していることは知っていますが、私はOTP以外の「デザインパターン」に興味があります。応答していただきありがとうございます。 – Max

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を開始したくないということです。たとえば、db_serverがすでに登録されている場合は、db_serverを生成するために1回、db_backup_serverを生成するために1回、start/0を2回呼び出します。お返事ありがとうございます。 – Max

+0

私はちょうどあなたより少し一般的なコードを作った。ちょうどスタート/ 0を持つように編集されました。 – cthulahoops

+0

私は、実際には失敗するのではなく、2台のサーバーを実際に起動したいと思っています。代わりにdb_backup_serverを登録するようにcatchブロックを編集し、3回コールされた場合にエラーを処理することができます。これは私にとっては厄介なデザインの選択のように思える、私はむしろ明示的に2つを開始するだろう – cthulahoops