183 lines
No EOL
7.5 KiB
Markdown
183 lines
No EOL
7.5 KiB
Markdown
z- Group together parts of the code that must look like atomic, in a way that is transparent, scalable and easy-to-use for the programmer
|
||
- Differently from monitors, the part of the code to group is not part of the definition of the objects, but is application dependent
|
||
- Differently from transactions in databases, the code can be any code, not just queries on the DB
|
||
|
||
**Transaction:** an atomic unit of computation (look like instantaneous and without overlap with any other transaction), that can access atomic objects.
|
||
when executed alone, every transaction successfully terminates.
|
||
|
||
**Program:** set of sequential processes, each alternating transactional and non-transactional code
|
||
|
||
**STM system:** online algorithm that has to ensure the atomic execution of the transactional code of the program.
|
||
|
||
To guarantee efficiency, all transactions can be executed at the same time (optimistic execution approach), but they must be totally ordered
|
||
- not always possible (where there are different accesses to the same object, with at least one of them that changes it)
|
||
- commit/abort transactions at their completion point (or even before)
|
||
- in case of abort, either try to re-execute or notify the invoking proc.
|
||
- possibility of unbounded delay
|
||
|
||
Conceptually, a transaction is composed of 3 parts:
|
||
`[READ of atomic reg’s] [local comput.] [WRITE into shared memory]`
|
||
|
||
The key issue is ensuring consistency of the shared memory
|
||
- as soon as some inconsistencies is discovered, the transaction is aborted
|
||
|
||
Implementation: every transaction uses a local working space
|
||
- For every shared register: the first READ copies the value of the reg. in the local copy; successive READs will then read from the local copy
|
||
- Every WRITE modifies the local copy and puts the final value in the shared memory only at the end of the transaction (if it has not been aborted)
|
||
|
||
4 operations:
|
||
- `begin_T()`: initializes the local control variables
|
||
- `X.read_T(), X.write_T()`: described above
|
||
- `try_to_commit_T()`: decides whether a transaction (non-aborted) can commit
|
||
|
||
#### A Logical Clock based STM system
|
||
All the READs perform if no inconsistencies arise, or before any inconsistency
|
||
|
||
(definizioni in def qua sotto)
|
||
>[!def]
|
||
>Let T be a transaction; its read prefix is formed by all its successful READ before its possible abortion.
|
||
|
||
>[!def]
|
||
>An execution is **opaque** if all committed transactions and all the read prefixes of all aborted transactions appear if executed one after the other, by following their real-time occurrence order.
|
||
|
||
We now present an atomic STM system, called *Transactional Locking 2*:
|
||
- CLOCK is an atomic READ/FETCH&ADD register initialized at 0
|
||
- Every MRMW register X is implemented by a pair of register XX s.t.
|
||
- XX.val contains the value of X
|
||
- XX.date contains the date (in terms of CLOCK) of the last update
|
||
- it is associated with a lock object to guarantee MUTEX when updating the shared memory
|
||
- For every transaction T, the invoking process maintains
|
||
- `lc(XX)`: a local copy of the implementation of reg. X
|
||
- `read_set(T)`: the set of names of all the registers read by T up to that moment
|
||
- `write_set(T)`: the set of names of all the registers written by T up to that moment
|
||
- `birthdate(T)`: the value of CLOCK(+1) at the starting of T
|
||
|
||
**Idea:** commit a transaction if and only if (iff) it could appear as executed at its birthdate time
|
||
**Consistency:**
|
||
- if T reads X, then it must be that `XX.date < birthdate(T)`
|
||
- to commit, all registers accessed by T cannot have been modified after T's birthdate (again, `XX.date < birthdate(T)`)
|
||
|
||
###### Implementation:
|
||
```
|
||
begin_T() :=
|
||
read_set(T), write_set(T) <- ∅
|
||
birthdate(T) <- CLOCK + 1
|
||
|
||
X.read_T() :=
|
||
if lc(XX) != ⊥ then
|
||
return lc(XX).val
|
||
lc(XX) <- XX
|
||
if lc(XX).date >= birthdate(T) then
|
||
ABORT
|
||
read_set(T) <- read_set(T) U {X}
|
||
return lc(XX).val
|
||
|
||
X.write_T(v) :=
|
||
if lc(XX) = ⊥ then
|
||
lc(XX) <- newloc
|
||
lc(XX).val <- v
|
||
write_set(T) <- write_set(T) U {X}
|
||
|
||
try_to_commit_T() :=
|
||
lock all read_set(T) U write_set(T)
|
||
∀ X ∈ read_set(T)
|
||
if XX.date >= birthdate(T) then
|
||
release all locks
|
||
ABORT
|
||
tmp <- CLOCK.fetch&add(1)+1
|
||
∀ X ∈ write_set(T)
|
||
XX <- ⟨lc(XX).val, tmp⟩
|
||
release all locks
|
||
COMMIT
|
||
```
|
||
|
||
### Virtual World Consistency
|
||
Opacity requires a total order on all committed transactions and on all read prefixes of all aborted transactions
|
||
this latter requirement can be weakened by imposing that the read prefix of an aborted transaction is consistent only w.r.t its casual past (i.e. its virtual world).
|
||
|
||
**Opacity:** total order both on all committed transactions and on read prefixes of aborted transactions
|
||
|
||
**VWC:** total order on all committed transactions + partial order on committed transactions and the read prefixes of aborted transactions.
|
||
|
||
The **casual past** of a transaction T is the set of all T' and T'' such that
|
||
- T reads a value written by T'
|
||
- T'' belongs to the casual past of T'
|
||
|
||
VWC allows more transactions to commit -> it is a more liberal property than opacity.
|
||
|
||

|
||
|
||
#### A Vector clock based STM system
|
||
We have m shared MRMW registers; register X is represented by a pair XX, with:
|
||
- `XX.val` the current value of X
|
||
- `XX.depend[1...m]` a vector clock s.t.
|
||
- `XX.depend[X]` is the sequence number associated with the current value of X
|
||
- corresponds to the `date` of the previous algorithm
|
||
- `XX.depend[Y]` is the sequence number associated with the value of Y on which the current value of X depends from
|
||
- There is a starvation-free lock object associated to the pair
|
||
|
||
So for X, I don't just have to track "who modified X", but also "who modified "Y or Z", if they may have influenced X.
|
||
|
||
We have n processes; process $p_i$ has
|
||
- for every X, a local copy `lc(XX)` of the implementation of X
|
||
- $p\_depend_i[1…m]$ s.t. $p\_depend_i[X]$ is the sequence number of the last value of X (directly or indirectly) known by $p_i$
|
||
|
||
Every transaction T issues by $p_i$ has:
|
||
- `read_set(T)` and `write_set(T)`
|
||
- $t\_depend_{T}[1…m]$ a local copy of $p\_depend_i$ (this is used in the optimistic execution, not to change $p\_depend_{i}$ if T aborts)
|
||
|
||
```
|
||
begin_T(i) :=
|
||
read_set(T), write_set(T) <- ∅
|
||
t_depend_T <- p_depend_i
|
||
|
||
X.read_T(i) :=
|
||
if lc(XX) = ⊥ then
|
||
lc(XX) <- newloc
|
||
lc(XX) <- XX
|
||
|
||
read_set(T) <- read_set(T) U {X}
|
||
|
||
t_depend_T[X] <- lc(XX).depend[X]
|
||
|
||
if ∃ Y ∈ read_set(T) s.t. t_dependT[Y] < lc(XX).depend[Y] then
|
||
# significa che il valore di X che ho letto è stato influenzato
|
||
# da una modifica di Y avvenuta dopo che T (questa trans.) ha letto Y
|
||
# per cui significa che, dopo che T ha letto Y, X è stato scritto
|
||
# da una trans. che ha scritto Y (e letto/scritto X)
|
||
# è lo stesso check che facevo prima ma non solo per X, ma per tutti i registri che ho letto fino ad ora.
|
||
ABORT
|
||
|
||
∀ Y ∉ read_set(T) do
|
||
t_depend_T[Y] <- max{t_depend_T[Y], lc(XX).depend[Y]}
|
||
# aggiorno la dipendenza da Y della transazione
|
||
# perché solo per quelli NON nel read_set?
|
||
# perché se dovessi farlo con un Y nel read_set dovrei abortire
|
||
# per i motivi di sopra! (e quindi in caso avrei già abortito)
|
||
|
||
return lc(XX).val
|
||
|
||
X.write_T(i, v) :=
|
||
if lc(XX) = ⊥ then
|
||
lc(XX) <- newloc
|
||
lc(XX).val <- v
|
||
write_set(T) <- write_set(T) U {X}
|
||
|
||
try_to_commit_T(i) :=
|
||
lock all read_set(T) U write_set(T)
|
||
|
||
if ∃ Y ∈ read_set(T) s.t. t_dependT[Y] < YY.depend[Y] then
|
||
release all locks
|
||
ABORT
|
||
# abortisco se ho letto un Y che è stato modificato dopo che io l'ho letto
|
||
|
||
∀ X ∈ write_set(T) do
|
||
t_dependT[X] <- XX.depend[X]+1
|
||
|
||
∀ X ∈ write_set(T) do
|
||
XX <- ⟨lc(XX).val, t_dependT⟩
|
||
|
||
release all locks
|
||
p_depend_i <- t_depend_T
|
||
COMMIT
|
||
``` |