erlang “参与者模型”如何解决“共享状态”问题?

bgibtngc  于 2023-02-20  发布在  Erlang
关注(0)|答案(2)|浏览(224)

“withdraw”porg似乎是个经典的例子,它在《sicp》和《程序设计语言中的设计概念》中被用来解释“共享状态”。
我想知道在“演员模型”中是否有一些方法可以避免“共享状态”?但是我找不到一个用erlang/elixir编写的好例子来展示它
在《编程erlang》第2版第22章中有一个withdraw的例子,但这个例子似乎说明了如何写opt,而不是如何处理“共享状态”:它使用ets数据库来保存“余额”,所以ets是“共享状态”,并且它只使用一个进程,而不是两个进程来“取款”和“存款”
那么,有没有“withdraw”的好例子来说明erlang/elixir是如何处理“共享状态”问题的呢?我认为它必须在消息中编码余额来处理它,并将“余额”传递到任何地方,以避免在固定的地方共享它。也许haskell的MVar会解决这个问题

0dxa2lsx

0dxa2lsx1#

一个actor,或者Erlang/Elixir进程,实际上是一个线程。如果你在GenServer的handle_call函数中,你可以保证在这个特定的消息处理程序完成之前,你不会收到另一个消息或者调用另一个handle_call。所有发送到进程的消息都是按照一定的顺序接收的,并且一次处理一个消息;在进程内没有并发性,因此没有机会并发地修改状态。
最小的Elixir设置可能如下所示

defmodule Account do
  use Genserver

  def start_link(balance) do
    GenServer.init(__MODULE__, balance)
  end

  def deposit(account, amount) do
    GenServer.call(account, {:deposit, amount})
  end

  def withdraw(account, amount) do
    GenServer.call(account, {:withdraw, amount})
  end

  @impl true
  def init(balance) do
    {:ok, balance}
  end

  @impl true
  def handle_call({:deposit, amount}, _, balance) do
    new_balance = balance + amount
    {:reply, :ok, new_balance}
  end

  @impl true
  def handle_call({:withdraw, amount}, _, balance) do
    if amount < balance do
      {:reply, {:error, :insufficient_balance}, balance}
    else
      new_balance = amount - balance
      {:reply, :ok, new_balance}
    end
  end
end

在具有可变状态的经典多线程环境中,一个线程有机会计算new_balance,而另一个线程覆盖现有余额,更改可能会丢失。(您引用了 Structure and Interpretation of Computer Programs,其中有整整一小节描述了这里的问题。)但是由于actor是单线程的,即使多个其他进程在同一个帐户上调用Account.withdraw/2,也可以保证获得一致的行为。

yvfmudvl

yvfmudvl2#

只是为了补充什么大卫迷宫解释:进程通过调用以下函数向OTP生成服务器发送消息:

gen_server:call(GenserverModuleName, Message)

当processA调用该函数时,会向genserver进程发送一条消息,例如在第22章中,该消息可能是撤销:{remove, "account0001", 200}。当processB调用该函数时,向genserver进程发送另一消息,例如另一撤销:genserver进程和所有的erlang进程一样,有一个邮箱,用于收集来自所有向它发送消息的进程的消息。
然后,生成服务器在邮箱中搜索它知道如何处理的消息,例如,与handle_call()函数定义的各个子句中指定的参数匹配的消息。然而,生成服务器一次仅处理一条消息,因此不存在竞争条件,即,两个进程试图同时更改同一数据块。比如账户余额,genserver将处理一条取款消息,如果账户余额足够大,则允许取款,并在ets表中更新余额,然后genserver将处理下一条取款消息,如果新的余额足够大,则允许第二次取款。并且余额在ets表中被更新。换句话说,genserver不分离两个过程来同时处理两个提款消息,而是genserver顺序地处理两个提款消息。
它使用ets数据库来保存“余额”,因此ets是“共享状态
genserver是唯一知道ets表的进程,并且genserver只按顺序访问ets表。
我认为它必须在消息中编码余额来处理它,并将“余额”传递到任何地方,以避免在固定的地方共享它。
不,余额可以保留在ets表中,原因如上所述。

相关问题