Lably
Skip to content

Transactional memory

Type of concurrency control mechanism

  • 8 min read
  • 4 chapters

Related on Lably

Through Computer science and Java (programming language)

Transactional memory systems provide high-level abstraction as an alternative to low-level thread synchronization. This abstraction allows for coordination between concurrent reads and writes of shared data in parallel systems.

Motivation

In concurrent programming, synchronization is required when parallel threads attempt to access a shared resource. Low-level thread synchronization constructs such as locks are pessimistic and prohibit threads that are outside a critical section from running the code protected by the critical section. The process of applying and releasing locks often functions as an additional overhead in workloads with little conflict among threads. Transactional memory provides optimistic concurrency control by allowing threads to run in parallel with minimal interference. The goal of transactional memory systems is to transparently support regions of code marked as transactions by enforcing atomicity, consistency and isolation.

A transaction is a collection of operations that can execute and commit changes as long as a conflict is not present. When a conflict is detected, a transaction will revert to its initial state (prior to any changes) and will rerun until all conflicts are removed. Before a successful commit, the outcome of any operation is purely speculative inside a transaction. In contrast to lock-based synchronization where operations are serialized to prevent data corruption, transactions allow for additional parallelism as long as few operations attempt to modify a shared resource. Since the programmer is not responsible for explicitly identifying locks or the order in which they are acquired, programs that utilize transactional memory cannot produce a deadlock.

With these constructs in place, transactional memory provides a high-level programming abstraction by allowing programmers to enclose their methods within transactional blocks. Correct implementations ensure that data cannot be shared between threads without going through a transaction and produce a serializable outcome. For example, code can be written as:

<span id="mwMQ"></span><span class="k" id="mwMg">class</span><span class="w" id="mwMw"> </span><span class="nc" id="mwNA">Account</span><span class="p" id="mwNQ">:</span> <span class="k" id="mwNg">def</span><span class="w" id="mwNw"> </span><span class="fm" id="mwOA">__init__</span><span class="p" id="mwOQ">(</span><span class="bp" id="mwOg">self</span><span class="p" id="mwOw">,</span> <span class="nb" id="mwPA">id</span><span class="p" id="mwPQ">:</span> <span class="nb" id="mwPg">int</span><span class="p" id="mwPw">,</span> <span class="n" id="mwQA">balance</span><span class="p" id="mwQQ">:</span> <span class="nb" id="mwQg">float</span><span class="p" id="mwQw">,</span> <span class="n" id="mwRA">name</span><span class="p" id="mwRQ">:</span> <span class="nb" id="mwRg">str</span><span class="p" id="mwRw">)</span> <span class="o" id="mwSA">-></span> <span class="kc" id="mwSQ">None</span><span class="p" id="mwSg">:</span> <span class="bp" id="mwSw">self</span><span class="o" id="mwTA">.</span><span class="n" id="mwTQ">id</span><span class="p" id="mwTg">:</span> <span class="nb" id="mwTw">int</span> <span class="o" id="mwUA">=</span> <span class="nb" id="mwUQ">id</span> <span class="bp" id="mwUg">self</span><span class="o" id="mwUw">.</span><span class="n" id="mwVA">balance</span><span class="p" id="mwVQ">:</span> <span class="nb" id="mwVg">float</span> <span class="o" id="mwVw">=</span> <span class="n" id="mwWA">balance</span> <span class="bp" id="mwWQ">self</span><span class="o" id="mwWg">.</span><span class="n" id="mwWw">name</span><span class="p" id="mwXA">:</span> <span class="nb" id="mwXQ">str</span> <span class="o" id="mwXg">=</span> <span class="n" id="mwXw">name</span> <span class="k" id="mwYA">def</span><span class="w" id="mwYQ"> </span><span class="nf" id="mwYg">transfer_money</span><span class="p" id="mwYw">(</span><span class="n" id="mwZA">from_account</span><span class="p" id="mwZQ">:</span> <span class="n" id="mwZg">Account</span><span class="p" id="mwZw">,</span> <span class="n" id="mwaA">to_account</span><span class="p" id="mwaQ">:</span> <span class="n" id="mwag">Account</span><span class="p" id="mwaw">,</span> <span class="n" id="mwbA">amount</span><span class="p" id="mwbQ">:</span> <span class="nb" id="mwbg">float</span><span class="p" id="mwbw">)</span> <span class="o" id="mwcA">-></span> <span class="kc" id="mwcQ">None</span><span class="p" id="mwcg">:</span> <span class="w" id="mwcw"> </span><span class="sd" id="mwdA">"""</span> <span class="sd" id="mwdQ"> Transfer money from one account to another.</span> <span class="sd" id="mwdg"> """</span> <span class="k" id="mwdw">with</span> <span class="n" id="mweA">transaction</span><span class="p" id="mweQ">():</span> <span class="n" id="mweg">from_account</span><span class="o" id="mwew">.</span><span class="n" id="mwfA">balance</span> <span class="o" id="mwfQ">-=</span> <span class="n" id="mwfg">amount</span> <span class="n" id="mwfw">to_account</span><span class="o" id="mwgA">.</span><span class="n" id="mwgQ">balance</span> <span class="o" id="mwgg">+=</span> <span class="n" id="mwgw">amount</span>

In the code, the block defined by "transaction" is guaranteed atomicity, consistency and isolation by the underlying transactional memory implementation and is transparent to the programmer. The variables within the transaction are protected from external conflicts, ensuring that either the correct amount is transferred or no action is taken at all. Note that concurrency related bugs are still possible in programs that use a large number of transactions, especially in software implementations where the library provided by the language is unable to enforce correct use. Bugs introduced through transactions can often be difficult to debug since breakpoints cannot be placed within a transaction.

Transactional memory is limited in that it requires a shared-memory abstraction. Although transactional memory programs cannot produce a deadlock, programs may still suffer from a livelock or resource starvation. For example, longer transactions may repeatedly revert in response to multiple smaller transactions, wasting both time and energy.

Sign in to keep reading

3 more chapters are free with a Lably account.

More in Computing and communication