I currently have a simple bank account written using erlang, I wish to add a mutex into it so that two deposits cannot be made where the set/get balance can be interupted so that the end value is wrong for example 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
My code is as follows:
-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).
I was wondering if someone could implement the mutex with brief annotations as to explain your thought process. Would be a huge help! Thanks again
The message queue of the account process can provide the effect you seek if you use it correctly. For example, your deposit/1
function has problems because it performs a read-modify-write, where the read and write are two separate actions. Because they're separate, they allow other unrelated actions to sneak in between them and break the math you're performing outside the account.
Why not instead make the account do its own math? After all, the account holds the funds, so it doesn't make sense to do the account calculations outside the account.
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.
With this approach, deposit/1
just adds funds atomically and returns the new balance:
deposit(Amount) when Amount > 0 ->
account_process ! {deposit, Amount, self()},
receive
{deposit, Amount, NewBalance} ->
{ok, NewBalance}
end.
Similarly, withdraw/1
just subtracts funds atomically if possible, returning the new balance, or returns an error if an overdraft would occur:
withdraw(Amount) when Amount > 0 ->
account_process ! {withdraw, Amount, self()},
receive
{withdrawal, Amount, NewBalance} ->
{ok, NewBalance};
Error ->
Error
end.
The get_bal/0
function remains the same.
With this approach, all transactions are atomic.