Unlike coarse-grained locking, transactions can provide scalability as long as the data-access patterns allow transactions to execute concurrently. The transaction system can provide good scalability in two ways:
- It can allow concurrent read operations to the same variable. In a parallel program, it’s safe to allow two or more threads to read the same variable concurrently. Basic mutual exclusion locks don’t permit concurrent readers; to allow concurrent readers, the programmer has to use special reader-writer locks, increasing the program’s complexity.
- It can allow concurrent read and write operations to different variables. In a parallel program, it’s safe to allow two or more threads to read and write disjoint variables concurrently. A programmer can explicitly code fine-grained disjoint access concurrency by associating different locks with different fine-grained data elements. This is usually a tedious and difficult task, however, and risks introducing bugs such as deadlocks and data races. Furthermore, as we show in a later example, fine-grained locking does not lend itself to modular software engineering practices: In general, a programmer can’t take software modules that use fine-grained locking and compose them together in a manner that safely allows concurrent access to disjoint data.
Transactions can be implemented in such a way that they allow both concurrent read accesses, as well as concurrent accesses to disjoint, fine-grained data elements (e.g., different objects or different array elements). Using transactions, the programmer gets these forms of concurrency without having to code them explicitly in the program.