STM implementations
Software Transactional Memory can be provided in various ways:
As a library
As a language construct
Libraries
Language support
- Haskell, Clojure, and Perl 6.
What about Erlang?
- We will implement it as a library! (inspired by this post )
STM in 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
andwrite
are used to locally read and write a variable, respectivelyFunction
pull
andpush
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, L) -> Bool %% update list of variables L in the TM store push(Tm, L) -> genserver:request(Tm, {push, L}).
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, L}) -> case update (Vars, L) of {true, NewVars} -> {reply, true, NewVars}; false -> {reply, false, Vars} end. update(Vars, [{Name, Ver, Value}|L]) -> {VerTM, _} = dict:fetch(Name, Vars), case Ver of VerTM -> update(dict:store(Name, {Ver+1, Value}, Vars), L); _ -> false end; update(Vars, []) -> {true, Vars}.
Composing transactions
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), case push(Tm, [NewBuff]) of true -> X ; false -> get_buff(Tm, Name) end.
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]), case push(Tm, [NewBuff]) of true -> ok ; false -> put_buff(Tm, Name, X) end.
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 modifypush
)Try implementing it! (advanced)
stm_get_put_buff(Tm, Name1, Name2) = atomic(fun get_put_buff/3, [Tm, Name1, Name2]).
Side-effects
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!)
The Linda model
1983: David Gelernter
General idea
- Parallel Programming = Computation + Coordination
Based on the blackboard communication
Asynchronous communication
No naming – global “blackboard”
Content matching
Linda, not a programming language
A collection of operations
Take your favorite (sequential) host language,
Add Linda operations
- Library
- Extend the language itself
You get a parallel/concurrent programming language
Blackboard - Tuple space
Linda shared memory
Blackboard – Tuple space
Data in the space: tuples
Tuples
- Typed sequences of data
- Singletons, Pairs, Triples, Quadruples, ...
Examples:
{1,true}
,{1,2}
,{false,2}
differently typed{"list",1,42}
Operations
postnote
puts a tuple into the space (never blocks); tuples may repeatremovenote
retrieves a tuple matching a pattern from the space (may block), removing itreadnote
retrieves a tuple matching a pattern from the space (may block), without removing it- Patterns are a similar concept to patterns in Erlang
Example:
Semaphore may be implemented using (many copies of) a single tuple
- Initialize the semaphore by putting N copies of
{sem, x}
, wherex
is the id of the semaphore. N is the number of permits. - The aqcuire operation performs
removenote({sem, x})
- The release operation performs
postnote({sem, x})
- Initialize the semaphore by putting N copies of
Producer-consumer may be implemented as follows
// producer int count = 0; while(true) { T d = produce(); postnote({buf, x, count, d}); count++; }
// consumer int count = 0; while (true) { T d; removenote({buf, x, count, d}); // d gets bound here consume(d); count++; }
- Easy to implement a bounded buffer
- When there are many producers/consumers, shared counters are also needed
Implementation considerations
- Requires an efficiently searchable database of tuples