Software Transactional Memory can be used in various ways:
As a library
As a language construct
Libraries
Language support
What about Erlang?
Create a server that keeps track of transactional variables and its versions (transactional memory store)
In a transaction, a variable is of the shape {Name, Version, Value}
Interface
Functions read
and write
are used to locally read and write a
variable, respectively
Function pull
and push
are used to fetch and commit variables
from and to the store, respectively.
%% new(Tm) -> ok %% create the transaction memory store (Tm) new(Tm) -> genserver:start(Tm, dict:new(), fun loop/2). %% add(Tm, Name) -> Var %% add a uninitialized variable to the Tm store add(Tm, Name) -> genserver:request(Tm, {add, Name, void}). %% add(Tm, Name, Init) -> Var %% add a initialized variable add(Tm, Name, Init) -> genserver:request(Tm, {add, Name, Init}). %% read(Var) -> Value %% return the value of a variable read({_Name, _Version, Value}) -> Value. %% write(Var, Value) -> ok %% write Value into Var write({Name, Version, _Value}, NewValue) -> {Name, Version, NewValue}. %% pull(Tm, Name) -> Var %% lookup variable Name in the Tm store pull(Tm, Name) -> genserver:request(Tm, {pull, Name}). %% push(Tm, Var) -> Bool %% update variable Var in the TM store push(Tm, Var) -> genserver:request(Tm, {push, Var}).
Transactional memory store
loop(Vars, {add, Name, Init}) -> case dict:find(Name, Vars) of {ok, _} -> {ok, Vars} ; error -> {ok, dict:store(Name, {0,Init}, Vars)} end ; loop(Vars, {pull, Name}) -> case dict:find(Name, Vars) of {ok, {Ver, Value}} -> {{Name, Ver, Value}, Vars} ; _ -> {error, Vars} end ; loop(Vars, {push, Name, {Name, Ver, Value}}) -> {VerTM, _} = dict:fetch(Name, Vars), case Ver of VerTM -> NewVars = dict:store(Name, {Ver+1, Value}, Vars), {true, NewVars} ; _ -> {false, Vars} end.
pull
and push
could fetch and commit several
variables at once
(check this post by Joe Armstrong)Composing transactions is embarrassingly simple
Transaction to get an element from a buffer
get_buff(Tm, Name) -> Buff = pull(Tm, Name), [X | Xs] = read(Buff), NewBuff = write(Buff, Xs), push(Tm, Name, NewBuff), X.
Transaction to remove the first element of a buffer
put_buff(Tm, Name, X) -> Buff = pull(Tm, Name), Xs = read(Buff), NewBuff = write(Buff, Xs ++ [X]), push(Tm, Name, NewBuff).
Transaction to get an element from one buffer into the other one
get_put_buff(Tm, Name1, Name2) -> Elem = get_buff(Tm, Name1), put_buff(Tm, Name2, Elem).
Simple, right? Is it completely atomic? How could you fix it?
Adding a function atomic(Function, [Args])
which differs
all the pushes until the end (need to modify push
)
Homework to implement it! It is fun!
stm_get_put_buff(Tm, Name1, Name2) = atomic(fun get_put_buff/3, [Tm, Name1, Name2]).
Side effects such as I/O don't mix very well with transactional memory
Programs raise a runtime exception if I/O is performed during a transaction
Issues like these make it difficult to implement and program with transactional memory in most languages (Why? Rollback!)
So far, message passing coupling dimensions
Time
Space
Time
Space
Communication by broadcast is decoupled
In space. Sender does not know who the receivers are, or how many receivers there are, or whether there are any receivers at all
But not in time. Receivers must exist and act simultaneously with the sender
Communication by blackboard is decoupled
In space
In time
Space
Writer does not know who the readers are, or
How many readers there are, or
Whether there are any readers at all
Time
1983: David Gelernter
General idea
Based on the blackboard communication
Asynchronous communication
No naming – global “blackboard”
Content matching
A collection of operations
Take your favorite (sequential) host language,
Add Linda operations
You get a parallel/concurrent programming language
Linda shared memory
Blackboard – Tuple space
Data in the space: tuples
Tuples
Examples:
{1,true}
, {1,2}
, {false,2}
differently typed{"list",1,42}
Check slides