2010-12-06 106 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