Похоже, что porg «снять» — это классический пример, он используется в «sicp» и «концепциях проектирования в языках программирования» для объяснения «общего состояния».
Я хочу знать, есть ли в «модели актера» какой-то способ избежать «общего состояния»? Но я не могу найти хороший пример записи в erlang/elixir, чтобы показать это.
Пример изъятия есть в 《programming erlang》2ed, глава 22, но пример, кажется, показывает, как написать opt, а не как обращаться с «общим состоянием»: он использует базу данных ets для сохранения «баланса», поэтому ets - это «общее состояние», и он использует только один процесс, а не два для «снятия» и «депозита».
Итак, есть ли хороший пример «снятия», чтобы показать, как erlang/elixir справляется с проблемой «общего состояния»? Я думаю, что для его обработки нужно кодировать баланс в сообщении и передавать «баланс» везде, чтобы избежать поделитесь им в месте исправления. Возможно, MVar haskell решит это
Снять "порг"? Серьезно, это опечатка или я что-то пропустил?
Актер или процесс Erlang/Elixir фактически является одним потоком. Если вы находитесь в функции handle_call GenServer, вы гарантированно не получите другое сообщение или не вызовете другое handle_call, пока этот конкретный обработчик сообщений не будет завершен. Все сообщения, отправленные процессу, принимаются в определенном порядке и обрабатываются по одному; в процессе нет параллелизма и, следовательно, нет возможности для одновременного изменения состояния.
Минимальная установка Эликсира может выглядеть так:
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, в то время как другой поток перезаписывает существующий баланс, и изменения могут быть потеряны. (Вы цитируете «Структура и интерпретация компьютерных программ», и здесь есть целый подраздел, описывающий проблемы.) Но поскольку актор является однопоточным, даже если несколько других процессов вызывают Account.withdraw/2 для одной и той же учетной записи, вы гарантированно получите непротиворечивое поведение.
Просто добавим к тому, что объяснил Дэвид Мейз: процесс отправляет сообщение генератору OTP, вызывая функцию:
gen_server:call(GenserverModuleName, Message)
Когда процесс A вызывает эту функцию, процессу genserver отправляется сообщение, например, в главе 22 сообщение может быть отзывом: {remove, "account0001", 200}. Когда процесс B вызывает эту функцию, процессу genserver отправляется другое сообщение, например. очередной вывод: {remove, "account001", 1000}. Процесс genserver, как и все процессы erlang, имеет почтовый ящик, в котором накапливаются сообщения от всех процессов, отправляющих ему сообщения.
Генсервер затем ищет в почтовом ящике сообщения, которые он знает, как обрабатывать, например. сообщения, соответствующие параметрам, указанным в различных пунктах определения функции handle_call(). Однако генсервер работает только с одним сообщением за раз, поэтому не может быть состояния гонки, т. е. когда два процесса пытаются одновременно изменить один и тот же фрагмент данных, например баланс учетной записи. Генсервер обработает одно сообщение о выводе средств, и если на счете достаточно большой баланс, то вывод будет разрешен, а баланс будет обновлен в таблице ets. Затем genserver обработает следующее сообщение о выводе средств, и, если новый баланс достаточно велик, будет разрешен второй вывод средств, а баланс будет обновлен в таблице ets. Другими словами, генератор-сервер не выделяет два процесса для одновременной обработки двух сообщений о снятии средств, а последовательно обрабатывает два сообщения о снятии средств.
он использует базу данных ets для сохранения «баланса», поэтому ets является «общим состояние
Genserver — единственный процесс, который знает о таблице ets, и genserver обращается к таблице ets только последовательно.
Я думаю, что он должен закодировать баланс в сообщении, чтобы обработать его, и передавайте «баланс» везде, чтобы не делиться им в фиксированном месте.
Нет, баланс может оставаться в таблице ets по указанным выше причинам.
Почтовый ящик Process решил бы эту проблему из коробки, если бы я правильно понял проблему.