Erlang中的互斥测试

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

我目前有一个使用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
我的代码初始代码如下:

-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风格函数调用,这些调用可能会以任意顺序更改内存中相同位置的基础值,而不会对它们进行锁定

过程