2010-12-06 3 views
2

Das folgende Codefragment ist Seite 112 des Buches von Francesco Cesarini und Simon Thompson, Erlang Programming, zur Illustration einer möglichen Wettlaufsituation in Erlang entnommen.Vermeiden einer Wettlaufsituation

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. 

Ohne die Details wörtlich zu kopieren, erklären die Autoren, dass, wenn zwei Prozesse gleichzeitig start() ausführen, dann 1 verarbeiten möglicherweise nicht abgeschlossen, die „undefiniert“ Abschnitt ausgeführt wird, weil Prozess 2 verursacht es vorbelegt werden. Der Prozess 2 würde dann den "undefinierten" Abschnitt zum Abschluss bringen. Jetzt, wenn Prozess 1 fortgesetzt wird, wurde db_server bereits von Prozess 2 registriert, wodurch der Aufruf von register() einen Laufzeitfehler auslöst. Ich hoffe du verstehst was ich meine, weil ich den Text des Buches nicht filtrieren will.

Meine Frage ist, wie kann die obige genaue Funktionalität codiert werden, um die mögliche Race Condition zu vermeiden, wenn zwei Prozesse gleichzeitig starten()?

Antwort

1

Wie viele Server möchten Sie starten? Ihre ursprüngliche Frage impliziert eine, während ein Kommentar zu @cthulahoops zwei sagt, ein Server und ein Backup. Für zwei Server könnten Sie etwas versuchen wie:

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. 

Ich habe es noch nicht ausgeführt.

+0

Ich möchte zwei Server starten. Ich habe ursprünglich das Beispiel aus dem O'Reilly-Buch benutzt, weil ich dachte, es wäre leichter für die Leute zu verstehen und daher meine Frage zu beantworten. Ich möchte genau 2 Server starten (ein primäres und ein Backup), um Failover bereitzustellen. Ich habe einen Code, der dem ähnlich ist, was Sie vorschlagen, aber vielleicht ist Ihr Rennen besser zu vermeiden. – Max

2

Sie könnten eine gen_server verwenden, um die Anfragen zu serialisieren.

+0

Ich weiß, OTP bietet robuste Lösungen für diese Art von Problemen, aber ich interessiere mich für nicht-OTP "Entwurfsmuster". Danke für die Antwort. – Max

9

Im Allgemeinen wird dies dadurch gelöst, dass der erzeugte Prozess seinen eigenen Namen registriert und dann eine Antwort an seinen Elternteil zurücksendet, die dem Elternteil mitteilt, ob es erfolgreich war oder nicht.

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. 

(Mai kompiliert oder Arbeit nicht. Hier typisiert, ohne zu überprüfen. :))

Sie können eine sorgfältig umgesetzt Version davon in gen.erl finden. In der Praxis verwenden Sie in echtem Code nur das OTP-Verhalten, um diese Version wiederzuverwenden.

+0

Das Problem für mich ist, dass ich Start/0 wie in der Frage verwenden möchte, und nicht starten/1 wie Sie vorschlagen. Zum Beispiel möchte ich start/0 zweimal aufrufen, einmal um db_server zu spawnen und einmal, um db_backup_server zu erzeugen, wenn db_server bereits registriert ist. Danke für das Antworten. – Max

+0

Ich habe den Code etwas generischer als deiner gemacht. Bearbeitet, um nur einen Anfang/0 zu haben. – cthulahoops

+0

Ich habe gerade entdeckt, dass Sie eigentlich zwei Server starten wollen, anstatt zu versagen. Sie könnten den catch-Block editieren, um stattdessen db_backup_server zu registrieren. Denken Sie daran, den Fehler zu behandeln, wenn er dreimal aufgerufen wird. Das scheint mir aber eine peinliche Design-Entscheidung zu sein, ich würde lieber die beiden – cthulahoops

Verwandte Themen