【问题标题】:Avoiding a race condition避免竞争条件
【发布时间】:2010-12-07 18:22:59
【问题描述】:

以下代码 sn-p 取自 Erlang 编程的 Francesco Cesarini 和 Simon Thompson 的书的第 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 恢复时,db_server 已经被进程 2 注册,导致其对 register() 的调用引发运行时错误。我希望你能明白我的意思,因为我不想盗用这本书的文字。

我的问题是,当两个进程同时执行 start() 时,如何对上述 exact 功能进行编码以避免潜在的竞争条件?

【问题讨论】:

    标签: erlang


    【解决方案1】:

    这通常通过让生成的进程注册自己的名称来解决,然后将响应发送回其父进程,告知父进程是否成功。

    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 行为来重用该版本。

    【讨论】:

    • 对我来说,问题是我想使用问题中的 start/0 ,而不是你建议的 start/1 。例如,如果 db_server 已经注册,我想调用 start/0 两次,一次生成 db_server,一次生成 db_backup_server。感谢您的回复。
    • 我只是让代码比你的更通用。编辑为只有一个开始/0。
    • 我刚刚发现您实际上想要启动两台服务器而不是失败。您可以编辑 catch 块来注册 db_backup_server,记住如果它被调用 3 次就处理错误。不过,这对我来说似乎是一个尴尬的设计选择,我宁愿明确地开始这两个
    • 我也是!不幸的是,我受到 API 的限制,它只允许我为主服务器和备份服务器提供 start/0。不过,您重新提出的建议对我来说确实不错。当然,没有正式的证据,我无法验证竞争条件不会发生。非常感谢您的帮助。
    【解决方案2】:

    您可以使用gen_server 来序列化请求。

    【讨论】:

    • 我知道 OTP 为这些类型的问题提供了强大的解决方案,但我对非 OTP 的“设计模式”感兴趣。感谢您的回复。
    【解决方案3】:

    您要启动多少台服务器?您最初的问题暗示一个,而对@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.
    

    我还没有运行它。

    【讨论】:

    • 我要启动两台服务器。我最初使用 O'Reilly 书中的示例,因为我认为人们更容易理解并因此回答我的问题。我想启动 2 台服务器(主服务器和备份服务器)以提供故障转移。我确实有与您建议的代码相似的代码,但也许您的代码更擅长避免竞争条件。
    猜你喜欢
    • 2015-01-30
    • 2010-09-25
    • 2010-09-25
    • 2019-06-12
    • 1970-01-01
    • 2020-01-16
    • 2014-04-02
    • 2017-10-04
    相关资源
    最近更新 更多