解决两个gen_tcp之间的死锁

时间:2011-05-17 20:50:21

标签: erlang gen-server

在浏览erlang应用程序的代码时,我遇到了一个有趣的设计问题。让我来描述一下情况,但由于PIA抱歉,我无法发布任何代码。

代码结构为OTP应用程序,其中两个gen_server模块负责分配某种资源。该应用程序运行完美一段时间,我们并没有真正的大问题。

当第一个gen_server需要检查第二个是否有足够的资源时,棘手的部分开始。向第二个gen_server发出call,该第二个gen_server本身调用一个实用程序库(在非常特殊的情况下)向第一个gen_server发出call

我对erlang比较陌生,但我认为这种情况会让两个gen_server相互等待。

这可能是一个设计问题,但我只是想知道OTP中是否有任何特殊的机制可以阻止这种“挂起”。

任何帮助都将不胜感激。

编辑: 总结答案:如果您有两个gen_server call以循环方式相互作用的情况,您最好在应用程序设计中花费更多时间。

感谢您的帮助:)

3 个答案:

答案 0 :(得分:4)

这称为死锁,可以/应该在设计级别避免。下面是一个可能的解决方法和一些主观点,希望可以帮助您避免犯错。

虽然有办法解决您的问题,但“等待”正是call正在做的事情。

一种可能的解决方法是从A内部生成一个调用B的进程,但不阻止A处理来自B的调用。这个进程将直接回复给调用者。

在服务器A中:

handle_call(do_spaghetti_call, From, State) ->
    spawn(fun() -> gen_server:reply(From, call_server_B(more_spaghetti)) end),
    {noreply, State};
handle_call(spaghetti_callback, _From, State) ->
    {reply, foobar, State}

在服务器B中:

handle_call(more_spaghetti, _From, State) ->
    {reply, gen_server:call(server_a, spaghetti_callback), State}

对我而言,这是非常复杂和超级理由。我想你甚至可以称之为意大利面条代码,而不会冒犯任何人。

另一方面,虽然上述内容可能会解决您的问题,但您应该认真考虑这样的呼叫实际意味着什么。例如,如果服务器A多次执行此调用会发生什么?如果在任何时候超时,会发生什么?你如何配置超时以便它们有意义? (最里面的调用必须比外部调用具有更短的超时等)。

我会改变设计,即使它很痛苦,因为当你允许它存在并解决它时,你的系统变得非常难以推理。恕我直言,复杂性是一切罪恶的根源,应该不惜一切代价避免。

答案 1 :(得分:3)

这主要是一个设计问题,您需要确保gen_server1没有长时间阻塞调用。这可以很容易地通过产生一个小小的乐趣来完成,它会处理你对gen_server2的调用,并在完成后将结果传递给gen_server1。

您必须跟踪gen_server1正在等待来自gen_server2的响应的事实。这样的事情可能是:

handle_call(Msg, From, S) ->
  Self = self(),
  spawn(fun() ->
    Res = gen_server:call(gen_server2, Msg),
    gen_server:cast(Self, {reply,Res})
  end),
{noreply, S#state{ from = From }}.

handle_cast({reply, Res}, S = #state{ from = From }) ->
  gen_server:reply(From, Res),
  {noreply, S#state{ from = undefiend}.

这样gen_server1可以在不挂起的情况下为gen_server2提供请求。你当然还需要对小过程进行适当的错误传播,但你会得到一般的想法。

答案 2 :(得分:1)

我认为更好的另一种方法是将此(资源)信息传递给异步。当每个服务器从另一个服务器获得(异步)my_resource_state消息时,它会做出反应并做它应该做的事情。它还可以提示其他服务器使用send_me_your_resource_state异步消息发送其资源状态。由于这两个消息都是异步的,因此它们永远不会阻塞,服务器可以在提示后等待来自其他服务器的my_resource_state消息时处理其他请求。

使消息异步的另一个好处是服务器可以在他们觉得有必要时发送而不提示这些信息,例如“帮助我,我的运行真的很低!”或者“我满溢,你想要一些吗?”。

来自@Lukas和@knutin的两个回复实际上是异步执行,但他们通过生成临时进程来执行此操作,然后可以执行同步调用而不会阻止服务器。直接使用异步消息更容易,并且意图也更清晰。