If your system behavior does not immediately depend on the value of what you're changing, then actors are fantastic. You don't wait, it's async. You send the data to the controlling actor (which is an object in the OO sense itself). Your process continues on as if nothing changed until you need the updated results.
Process A
B ! {update, x, newVal},
%% A bunch of work
B ! {lookup, y, A}, % where y perhaps depended on x
Y = receive % Here we finally wait, if B is processing a lot of requests
{B,y,Val} -> Val
end,
%% more work
Process B
loop(State) ->
receive
{update, Var, Val} -> loop(update(State,Var,Val));
{lookup, Var, Pid} -> Pid ! lookup(State, Var), loop(State)
end.
Where B may be much more complex than that, in this case it looks like a simple KV store.The receive at the end of Process A is blocking and is the only time we end up with a lock in Process A. So if A never needs to do anything but send values to B, then A can run undisturbed by everyone else's data requests. This lends itself well to certain pipelined and dataflow architectures.
The main practical drawback, I would say, is that locks may suffer from the mutual exclusion problem; where you have contention, starvation and deadlocking. Also your ability to parallelize operations over shared state quickly vanishes as the number of threads grow...