2.4 Optimistic Approaches
2.4.4 Coordinating Transactions
Transactional memory semantics alone do not consider the effects of ordering transaction execution. Rather, much existing research has focused on the pro- duction of transactional schedules which are serializable. When transactions were originally implemented in distributed database applications, transaction ordering was less of a concern because transactions tend to be independent operations. In transactional memory, however, transactions tend to be tightly coupled and coordination becomes more prominent an issue. (For instance, producer and con- sumer transactions may be modifying a shared buffer; a producer transaction must precede a consumer transaction if the buffer is empty.)
In this section two mechanisms are covered, which provide the application pro- grammer with the ability to coordinate transactions, namely: conditional primi- tives and transactional nesting.
Figure 2.6: Transaction Coordination Thread P-2 aborts its transaction ex- plicitly in this scenario because the shared buffer has reached capacity after Thread P-1’s transaction. Once Thread C-1 removes an item from the buffer, Thread P-2 can retry and commit.
Conditional Primitives A number of primitives have been developed to pro- vide the programmer who values transaction coordination with greater flexibility. These include the ability to explicitly abort a transaction and the retry and orElse statements first described by Harris et al [14]:
1. Programming Languages which support exception handling allow a trans- action to be terminated prematurely and explicitly by the programmer. A transaction may throw an exception from within itself, to be caught back in the application. Some implementations of transactional memory also pro- vide the keyword abort so that a transaction can be aborted and rolled-back explicitly.
2. Harris et al [14] first introduced the retry statement into transactional se- mantics. If a transaction reaches a retry statement, the transaction is al- lowed to abort for whatever arbitrary reason the programmer specifies, and another attempt is then made to execute the transaction.
3. The orElse statement (also courtesy of Harris et al [14]) provides conditional coordination between two transactions. Using orElse, an expression like (T1 orElse T2) means that if transaction T1 commits then T2 will not be
orElse statement is completed, but if T2 also aborts, the orElse statement
is re-executed.
With coordination primitives, sophisticated orderings of transaction execution can be expressed at the cost of requiring application programmers to explicitly program transaction coordination themselves. Because these decisions tend to be specific to each particular application, this may be a necessity. However, sophisticated coordination between parallel components will cause difficulty for application programmers, raising the risk of introducing consistency errors. (For example, use of the retry or orElse may result in a live-lock).
Nested Transactions Transactions that execute wholly within the execution of another (parent) transaction are called nested transactions. By accommodating nested transactions, transactional memory can provide the following benefits: Composition – Analogous to the use of functions in most programming lan-
guages, allowing one transaction to be executed from inside another trans- action is desirable because it allows transactions to be composed, just as an atomic block allows concurrent statements to be composed.
Coordination – Nested transactions provide a degree of transaction coordina- tion which is absent from the general purpose transactional semantics of commit or abort.
Efficiency – Under some approaches to nested transactions, rolling-back the ef- fects of a nested transaction does not automatically abort its parent trans- action. This is beneficial if the nested transaction can retry and commit because the parent transaction does not have to undo its changes and exe- cute repeatedly.
Although conceptually intuitive, in practice the management of nested trans- actions requires a substantial degree of complexity to implement, and a number of approaches which accommodate nested transactions have thus far been devel- oped:
Flattened Transactions – Flattened transactions are the easiest to implement; the nested transactions are simply incorporated into the parent transaction and the result is executed as a single transaction. However, they offer only ‘syntactic sugar’ to the user, and the parent transaction aborts along with any child transactions.
Closed Transactions – If a child transaction aborts then this does not cause its parent transaction to abort. If a child transaction commits then its changes are immediately observable to the parent transaction, but not to any other transaction. The behaviour of closed transactions and flattened transactions is indistinguishable when the transactions commit.
Open Transactions – When an open transaction commits, its changes are im- mediately observable to any other transaction in the system. This is true regardless of the state of the parent transaction and whether the parent transaction commits or aborts. Thus open transactions allow nested sec- tions of transactional code to execute unrelated tasks (such as memory management), which the application programmer does not want to be un- done.
A number of techniques have been developed recently to allow greater par- allelism when executing nested transactions. For example, both HParSTM [15] and [16], describe schemes to allow the parallel execution of nested transactions. It should be noted that while not all applications will necessarily benefit from or require the use of nested transactions, those that do tend to feature in the domain of transactional memory (as opposed to database transactions). Where processes or threads are executing transactions in memory, there tends to be a greater need for coordination around accesses of shared data. Object orientated programming is no exception to this pattern, as shared data tends to be grouped logically into shared objects.