Erlang中的互斥测试
我目前有一个使用erlang编写的简单银行账户,我也有一个相同的银行账户被重写,目的是为了相互隔离。这个想法是,在设置/获取余额可以中断的情况下,无法进行两次存款,因此最终值是错误的,例如bal a=10 bal B=20:Erlang中的互斥测试,erlang,mutex,mutual-exclusion,Erlang,Mutex,Mutual Exclusion,我目前有一个使用erlang编写的简单银行账户,我也有一个相同的银行账户被重写,目的是为了相互隔离。这个想法是,在设置/获取余额可以中断的情况下,无法进行两次存款,因此最终值是错误的,例如bal a=10 bal B=20: WRONG get_bal.A 0 → get_bal.B 0 → set_bal.A 10 → set_bal.B 20 == 20 RIGHT get_bal.A 0 → set_bal.A 10 → get_bal.B 10 → set_bal.B 30 == 30
WRONG
get_bal.A 0 → get_bal.B 0 → set_bal.A 10 → set_bal.B 20 == 20
RIGHT
get_bal.A 0 → set_bal.A 10 → get_bal.B 10 → set_bal.B 30 == 30
我的代码初始代码如下:
-module(bank).
-export([account/1, start/0, stop/0, deposit/1, get_bal/0, set_bal/1]).
account(Balance) ->
receive
{set, NewBalance} ->
account(NewBalance);
{get, From} ->
From ! {balance, Balance},
account(Balance);
stop -> ok
end.
start() ->
Account_PID = spawn(bank, account, [0]),
register(account_process, Account_PID).
stop() ->
account_process ! stop,
unregister(account_process).
set_bal(B) ->
account_process ! {set, B}.
get_bal() ->
account_process ! {get, self()},
receive
{balance, B} -> B
end.
deposit(Amount) ->
OldBalance = get_bal(),
NewBalance = OldBalance + Amount,
set_bal(NewBalance).
account(Balance) ->
receive
{deposit, Amount, From} ->
NewBalance = Balance + Amount,
From ! {deposit, Amount, NewBalance},
account(NewBalance);
{withdraw, Amount, From} when Amount > Balance ->
From ! {error, {insufficient_funds, Amount, Balance}},
account(Balance);
{withdraw, Amount, From} ->
NewBalance = Balance - Amount,
From ! {withdrawal, Amount, NewBalance},
account(NewBalance);
{get, From} ->
From ! {balance, Balance},
account(Balance);
stop -> ok
end.
deposit(Amount) when Amount > 0 ->
account_process ! {deposit, Amount, self()},
receive
{deposit, Amount, NewBalance} ->
{ok, NewBalance}
end.
withdraw(Amount) when Amount > 0 ->
account_process ! {withdraw, Amount, self()},
receive
{withdrawal, Amount, NewBalance} ->
{ok, NewBalance};
Error ->
Error
end.
这个想法是建立一个测试,这样我可以收到一个错误,如果最终平衡可能是错误的,并通过,如果它按计划进行。
我重新编写的代码如下:
-module(bank).
-export([account/1, start/0, stop/0, deposit/1, get_bal/0, set_bal/1]).
account(Balance) ->
receive
{set, NewBalance} ->
account(NewBalance);
{get, From} ->
From ! {balance, Balance},
account(Balance);
stop -> ok
end.
start() ->
Account_PID = spawn(bank, account, [0]),
register(account_process, Account_PID).
stop() ->
account_process ! stop,
unregister(account_process).
set_bal(B) ->
account_process ! {set, B}.
get_bal() ->
account_process ! {get, self()},
receive
{balance, B} -> B
end.
deposit(Amount) ->
OldBalance = get_bal(),
NewBalance = OldBalance + Amount,
set_bal(NewBalance).
account(Balance) ->
receive
{deposit, Amount, From} ->
NewBalance = Balance + Amount,
From ! {deposit, Amount, NewBalance},
account(NewBalance);
{withdraw, Amount, From} when Amount > Balance ->
From ! {error, {insufficient_funds, Amount, Balance}},
account(Balance);
{withdraw, Amount, From} ->
NewBalance = Balance - Amount,
From ! {withdrawal, Amount, NewBalance},
account(NewBalance);
{get, From} ->
From ! {balance, Balance},
account(Balance);
stop -> ok
end.
deposit(Amount) when Amount > 0 ->
account_process ! {deposit, Amount, self()},
receive
{deposit, Amount, NewBalance} ->
{ok, NewBalance}
end.
withdraw(Amount) when Amount > 0 ->
account_process ! {withdraw, Amount, self()},
receive
{withdrawal, Amount, NewBalance} ->
{ok, NewBalance};
Error ->
Error
end.
感谢您的阅读,我们将非常感谢您的帮助。在Erlang中,互斥不是问题进程是参与者,它们之间不共享内存。 看看这个问题: 至于代码,我可能会这样做(“银行”表示为gen_服务器)。这并不是解决您问题的真正方法,而是使用OTP实现相同目标的另一种方法:
-module(bank).
-behaviour(gen_server).
%% API
-export([start_link/0, new_account/1, withdraw/2, deposit/2, get_bal/1]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-record(state, {accounts = [] :: list()}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
new_account(Name) ->
gen_server:call(?MODULE, {new_account, Name}).
deposit(Account, Amount) when Amount > 0 ->
gen_server:call(?MODULE, {deposit, Account, Amount}).
withdraw(Account, Amount) when Amount > 0 ->
gen_server:call(?MODULE, {withdraw, Account, Amount}).
get_bal(Account) ->
gen_server:call(?MODULE, {get_bal, Account}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call({new_account, Name}, _From, State) ->
Accounts = State#state.accounts,
case find_account(Name, Accounts) of
none ->
{reply, {account_created, Name}, State#state{accounts=[{Name, 0}|Accounts]}};
_ ->
{reply, already_exists, State}
end;
handle_call({get_bal, Account}, _From, State) ->
Accounts = State#state.accounts,
{_Name, Balance} = find_account(Account, Accounts),
{reply, Balance, State};
handle_call({deposit, Account, Amount}, _From, State) ->
Accounts = State#state.accounts,
{Name, Balance} = find_account(Account, Accounts),
NewBalance = Balance + Amount,
NewAccounts = lists:keyreplace(Name, 1, Accounts, {Name, NewBalance}),
{reply, {deposit, Amount, NewBalance}, State#state{accounts=NewAccounts}};
handle_call({withdraw, Account, Amount}, _From, State) ->
Accounts = State#state.accounts,
{Name, Balance} = find_account(Account, Accounts),
case Amount of
Amount when Amount > Balance ->
{reply, {insufficient_funds, Amount, Balance}, State};
_ ->
NewBalance = Balance - Amount,
NewAccounts = lists:keyreplace(Name, 1, Accounts, {Name, NewBalance}),
{reply, {withdrawal, Amount, NewBalance}, State#state{accounts=NewAccounts}}
end;
handle_call(_Request, _From, State) ->
Reply = not_implemented,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
find_account(Account, Accounts) ->
proplists:lookup(Account, Accounts).
在Erlang中,互斥不是问题进程是参与者,它们之间不共享内存。 看看这个问题: 至于代码,我可能会这样做(“银行”表示为gen_服务器)。这并不是解决您问题的真正方法,而是使用OTP实现相同目标的另一种方法:
-module(bank).
-behaviour(gen_server).
%% API
-export([start_link/0, new_account/1, withdraw/2, deposit/2, get_bal/1]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-record(state, {accounts = [] :: list()}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
new_account(Name) ->
gen_server:call(?MODULE, {new_account, Name}).
deposit(Account, Amount) when Amount > 0 ->
gen_server:call(?MODULE, {deposit, Account, Amount}).
withdraw(Account, Amount) when Amount > 0 ->
gen_server:call(?MODULE, {withdraw, Account, Amount}).
get_bal(Account) ->
gen_server:call(?MODULE, {get_bal, Account}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call({new_account, Name}, _From, State) ->
Accounts = State#state.accounts,
case find_account(Name, Accounts) of
none ->
{reply, {account_created, Name}, State#state{accounts=[{Name, 0}|Accounts]}};
_ ->
{reply, already_exists, State}
end;
handle_call({get_bal, Account}, _From, State) ->
Accounts = State#state.accounts,
{_Name, Balance} = find_account(Account, Accounts),
{reply, Balance, State};
handle_call({deposit, Account, Amount}, _From, State) ->
Accounts = State#state.accounts,
{Name, Balance} = find_account(Account, Accounts),
NewBalance = Balance + Amount,
NewAccounts = lists:keyreplace(Name, 1, Accounts, {Name, NewBalance}),
{reply, {deposit, Amount, NewBalance}, State#state{accounts=NewAccounts}};
handle_call({withdraw, Account, Amount}, _From, State) ->
Accounts = State#state.accounts,
{Name, Balance} = find_account(Account, Accounts),
case Amount of
Amount when Amount > Balance ->
{reply, {insufficient_funds, Amount, Balance}, State};
_ ->
NewBalance = Balance - Amount,
NewAccounts = lists:keyreplace(Name, 1, Accounts, {Name, NewBalance}),
{reply, {withdrawal, Amount, NewBalance}, State#state{accounts=NewAccounts}}
end;
handle_call(_Request, _From, State) ->
Reply = not_implemented,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
find_account(Account, Accounts) ->
proplists:lookup(Account, Accounts).
在Erlang中,互斥不是问题进程是参与者,它们之间不共享内存。 看看这个问题: 至于代码,我可能会这样做(“银行”表示为gen_服务器)。这并不是解决您问题的真正方法,而是使用OTP实现相同目标的另一种方法:
-module(bank).
-behaviour(gen_server).
%% API
-export([start_link/0, new_account/1, withdraw/2, deposit/2, get_bal/1]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-record(state, {accounts = [] :: list()}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
new_account(Name) ->
gen_server:call(?MODULE, {new_account, Name}).
deposit(Account, Amount) when Amount > 0 ->
gen_server:call(?MODULE, {deposit, Account, Amount}).
withdraw(Account, Amount) when Amount > 0 ->
gen_server:call(?MODULE, {withdraw, Account, Amount}).
get_bal(Account) ->
gen_server:call(?MODULE, {get_bal, Account}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call({new_account, Name}, _From, State) ->
Accounts = State#state.accounts,
case find_account(Name, Accounts) of
none ->
{reply, {account_created, Name}, State#state{accounts=[{Name, 0}|Accounts]}};
_ ->
{reply, already_exists, State}
end;
handle_call({get_bal, Account}, _From, State) ->
Accounts = State#state.accounts,
{_Name, Balance} = find_account(Account, Accounts),
{reply, Balance, State};
handle_call({deposit, Account, Amount}, _From, State) ->
Accounts = State#state.accounts,
{Name, Balance} = find_account(Account, Accounts),
NewBalance = Balance + Amount,
NewAccounts = lists:keyreplace(Name, 1, Accounts, {Name, NewBalance}),
{reply, {deposit, Amount, NewBalance}, State#state{accounts=NewAccounts}};
handle_call({withdraw, Account, Amount}, _From, State) ->
Accounts = State#state.accounts,
{Name, Balance} = find_account(Account, Accounts),
case Amount of
Amount when Amount > Balance ->
{reply, {insufficient_funds, Amount, Balance}, State};
_ ->
NewBalance = Balance - Amount,
NewAccounts = lists:keyreplace(Name, 1, Accounts, {Name, NewBalance}),
{reply, {withdrawal, Amount, NewBalance}, State#state{accounts=NewAccounts}}
end;
handle_call(_Request, _From, State) ->
Reply = not_implemented,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
find_account(Account, Accounts) ->
proplists:lookup(Account, Accounts).
在Erlang中,互斥不是问题进程是参与者,它们之间不共享内存。 看看这个问题: 至于代码,我可能会这样做(“银行”表示为gen_服务器)。这并不是解决您问题的真正方法,而是使用OTP实现相同目标的另一种方法:
-module(bank).
-behaviour(gen_server).
%% API
-export([start_link/0, new_account/1, withdraw/2, deposit/2, get_bal/1]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-record(state, {accounts = [] :: list()}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
new_account(Name) ->
gen_server:call(?MODULE, {new_account, Name}).
deposit(Account, Amount) when Amount > 0 ->
gen_server:call(?MODULE, {deposit, Account, Amount}).
withdraw(Account, Amount) when Amount > 0 ->
gen_server:call(?MODULE, {withdraw, Account, Amount}).
get_bal(Account) ->
gen_server:call(?MODULE, {get_bal, Account}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call({new_account, Name}, _From, State) ->
Accounts = State#state.accounts,
case find_account(Name, Accounts) of
none ->
{reply, {account_created, Name}, State#state{accounts=[{Name, 0}|Accounts]}};
_ ->
{reply, already_exists, State}
end;
handle_call({get_bal, Account}, _From, State) ->
Accounts = State#state.accounts,
{_Name, Balance} = find_account(Account, Accounts),
{reply, Balance, State};
handle_call({deposit, Account, Amount}, _From, State) ->
Accounts = State#state.accounts,
{Name, Balance} = find_account(Account, Accounts),
NewBalance = Balance + Amount,
NewAccounts = lists:keyreplace(Name, 1, Accounts, {Name, NewBalance}),
{reply, {deposit, Amount, NewBalance}, State#state{accounts=NewAccounts}};
handle_call({withdraw, Account, Amount}, _From, State) ->
Accounts = State#state.accounts,
{Name, Balance} = find_account(Account, Accounts),
case Amount of
Amount when Amount > Balance ->
{reply, {insufficient_funds, Amount, Balance}, State};
_ ->
NewBalance = Balance - Amount,
NewAccounts = lists:keyreplace(Name, 1, Accounts, {Name, NewBalance}),
{reply, {withdrawal, Amount, NewBalance}, State#state{accounts=NewAccounts}}
end;
handle_call(_Request, _From, State) ->
Reply = not_implemented,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
find_account(Account, Accounts) ->
proplists:lookup(Account, Accounts).
答案类似于“改进Erlang中的瓶颈性能”。在出现瓶颈的情况下,目标不是改进它(使它更具性能),而是完全消除它(这几乎是不可能的) 在排除的情况下,目标不是证明您“在过程Y和回滚到Z的过程中锁定了数据X”,而是以完全不需要锁定的方式编写程序。我确信有些情况下这是无法避免的,但我从未在Erlang遇到过这样的情况(至少我记不起来了)。进程不共享内存。这就是为什么Steve Vinoski对您之前(几乎相同)问题的回答()演示了如何组合操作,而不是在外部流程API中划分操作步骤 如果您公开了一个过程
add(Account,Value)
,该过程只执行循环(Current+Value)
,而没有发生任何其他事情,那么您一定是在自找麻烦。但这暴露了一个极低级别的API,不是吗?解决这个问题的正确方法是按照Vinoski的建议,只公开一个更高级别的API,该API将值更改的操作与报告更改的效果结合起来。当您现在尝试读取该值时,来自另一个进程的另一个相同形式的挂起操作不可能更改该值,从而导致一个或另一个API调用出错,因为API调用是排队的消息,不是在不同线程之间发生的C风格函数调用,这些调用可能会以任意顺序更改内存中相同位置的基础值,而不会对它们进行锁定
进程邮箱是您的互斥体。如果您按照预期的方式使用Erlang进程,那么这类错误根本不存在。你不能把事情搞砸。每个操作都是完全原子的,按消息接收顺序排队,完全阻塞/锁定数据,并且在任何情况下都无法从外部访问底层数据
每个进程在其存在期间对其所有数据都有一个独占锁。答案类似于“改进Erlang中的瓶颈性能”。在出现瓶颈的情况下,目标不是改进它(使它更具性能),而是完全消除它(这几乎是不可能的) 在排除的情况下,目标不是证明您“在过程Y和回滚到Z的过程中锁定了数据X”,而是以完全不需要锁定的方式编写程序。我确信有些情况下这是无法避免的,但我从未在Erlang遇到过这样的情况(至少我记不起来了)。进程不共享内存。这就是为什么Steve Vinoski对您之前(几乎相同)问题的回答()演示了如何组合操作,而不是在外部流程API中划分操作步骤 如果您公开了一个过程
add(Account,Value)
,该过程只执行循环(Current+Value)
,而没有发生任何其他事情,那么您一定是在自找麻烦。但这暴露了一个极低级别的API,不是吗?解决这个问题的正确方法是按照Vinoski的建议,只公开一个更高级别的API,该API将值更改的操作与报告更改的效果结合起来。当您现在尝试读取该值时,来自另一个进程的另一个相同形式的挂起操作不可能更改该值,从而导致一个或另一个API调用出错,因为API调用是排队的消息,不是在不同线程之间发生的C风格函数调用,这些调用可能会以任意顺序更改内存中相同位置的基础值,而不会对它们进行锁定
过程