Concurrent Objects and Linearizability

Similar documents
Concurrent Objects. Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit

Programming Paradigms for Concurrency Lecture 3 Concurrent Objects

Review of last lecture. Goals of this lecture. DPHPC Overview. Lock-based queue. Lock-based queue

Review of last lecture. Peer Quiz. DPHPC Overview. Goals of this lecture. Lock-based queue

Section 4 Concurrent Objects Correctness, Progress and Efficiency

The Relative Power of Synchronization Methods

Overview of Lecture 4. Memory Models, Atomicity & Performance. Ben-Ari Concurrency Model. Dekker s Algorithm 4

A Non-Blocking Concurrent Queue Algorithm

Thread-Local. Lecture 27: Concurrency 3. Dealing with the Rest. Immutable. Whenever possible, don t share resources

Introduction. Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit

Safety SPL/2010 SPL/20 1

Introduction to Concurrency and Multicore Programming. Slides adapted from Art of Multicore Programming by Herlihy and Shavit

Unit 6: Indeterminate Computation

Sequen&al Consistency and Linearizability

The Wait-Free Hierarchy

Linearizability of Persistent Memory Objects

A simple correctness proof of the MCS contention-free lock. Theodore Johnson. Krishna Harathi. University of Florida. Abstract

DISTRIBUTED COMPUTER SYSTEMS

Important Lessons. A Distributed Algorithm (2) Today's Lecture - Replication

On the Definition of Sequential Consistency

Quasi-Linearizability Relaxed Consistency For Improved Concurrency

10/27/11. Is The Queue Correct? Concurrent Computaton. Objects. A Concurrent FIFO Queue head. A Concurrent FIFO Queue head

Outline of lecture. i219 Software Design Methodology 10. Multithreaded programming. Kazuhiro Ogata (JAIST)

Chapter 4 Defining Classes I

1 The comparison of QC, SC and LIN

Experience with Model Checking Linearizability

CSE332: Data Abstractions Lecture 23: Programming with Locks and Critical Sections. Tyler Robison Summer 2010

+ Today. Lecture 26: Concurrency 3/31/14. n Reading. n Objectives. n Announcements. n P&C Section 7. n Race conditions.

CSL373: Lecture 5 Deadlocks (no process runnable) + Scheduling (> 1 process runnable)

G Programming Languages Spring 2010 Lecture 13. Robert Grimm, New York University

Lecture 10 Notes Linked Lists

ACONCURRENT system may be viewed as a collection of

Synchronization in Java

The Universality of Consensus

DD2451 Parallel and Distributed Computing --- FDD3008 Distributed Algorithms

Concurrent specifications beyond linearizability

Generic Proofs of Consensus Numbers for Abstract Data Types

Fork Sequential Consistency is Blocking

Fork Sequential Consistency is Blocking

Ownership of a queue for practical lock-free scheduling

Concurrent Computing. January 21, 2018

Lecture Notes on Queues

Interprocess Communication By: Kaushik Vaghani

Lock-free Serializable Transactions

Linearizability Testing Manual

DB2 Lecture 10 Concurrency Control

A Sophomoric Introduction to Shared-Memory Parallelism and Concurrency Lecture 5 Programming with Locks and Critical Sections

Concurrent Queues, Monitors, and the ABA problem. Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit

Mutual Exclusion: Classical Algorithms for Locks

Shared Objects. Shared Objects

Lecture Notes on Memory Layout

i219 Software Design Methodology 11. Software model checking Kazuhiro Ogata (JAIST) Outline of lecture

On the Correctness of Transactional Memory

arxiv: v8 [cs.dc] 29 Jul 2011

Chapter 13 : Concurrency Control

Lecture 21: Transactional Memory. Topics: consistency model recap, introduction to transactional memory

3.7 Denotational Semantics

Composing Concurrency Control

Verification of a Concurrent Deque Implementation

Computation Abstractions. Processes vs. Threads. So, What Is a Thread? CMSC 433 Programming Language Technologies and Paradigms Spring 2007

Programmazione di sistemi multicore

Kakadu and Java. David Taubman, UNSW June 3, 2003

Time and Space Lower Bounds for Implementations Using k-cas

The New Java Technology Memory Model

CS 161 Computer Security

Reminder from last time

Order Is A Lie. Are you sure you know how your code runs?

COMP Parallel Computing. CC-NUMA (2) Memory Consistency

Monitors; Software Transactional Memory

Relaxed Memory-Consistency Models

Concurrent Programming using Threads

Temporal Logic of Actions (TLA) (a brief introduction) Shmuel Katz Computer Science Department The Technion

Thirty one Problems in the Semantics of UML 1.3 Dynamics

Implementation of Process Networks in Java

Lecture 4: Languages, Fast Locks, and Lock-free

Solo-Valency and the Cost of Coordination

Lecture 21 Concurrency Control Part 1

Local Linearizability

Whatever can go wrong will go wrong. attributed to Edward A. Murphy. Murphy was an optimist. authors of lock-free programs 3.

1 Process Coordination

Lecture 1 Contracts : Principles of Imperative Computation (Fall 2018) Frank Pfenning

Lecture 7: Mutual Exclusion 2/16/12. slides adapted from The Art of Multiprocessor Programming, Herlihy and Shavit

Models of concurrency & synchronization algorithms

Whatever can go wrong will go wrong. attributed to Edward A. Murphy. Murphy was an optimist. authors of lock-free programs LOCK FREE KERNEL

CONCURRENT LIBRARIES. Correctness Criteria, Verification

Concurrent Queues and Stacks. Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit

Lecture 10 Notes Linked Lists

Data Abstraction and Specification of ADTs

Using a waiting protocol to separate concerns in the mutual exclusion problem

k-abortable Objects: Progress under High Contention

6.033: Fault Tolerance: Isolation Lecture 17 Katrina LaCurts,

Synchronization. Chapter 5

Parallel Programming in Distributed Systems Or Distributed Systems in Parallel Programming

Chapter 15 : Concurrency Control

1 Introduction. Michał Kapałka EPFL, Switzerland Rachid Guerraoui EPFL, Switzerland

Lecture: Consistency Models, TM. Topics: consistency models, TM intro (Section 5.6)

Generic Proofs of Consensus Numbers for Abstract Data Types

3/25/14. Lecture 25: Concurrency. + Today. n Reading. n P&C Section 6. n Objectives. n Concurrency

Shared memory model" Shared memory guarantees" Read-write register" Wait-freedom: unconditional progress " Liveness" Shared memory basics"

The Java Memory Model

Transcription:

Chapter 3 Concurrent Objects and Linearizability 3.1 Specifying Objects An object in languages such as Java and C++ is a container for data. Each object provides a set of methods that are the only way to to manipulate that object s state. Each object has a class which describes how its methods behave. There are many ways to describe an object s behavior, ranging from formal specifications to plain English. The application programmer interface (API) documentation that we use every day lies somewhere between mathematical rigor and conversational chatter. This documentation typically says something like the following: If the object is in such-and-such a state before you call the method, then the object will be in some other state when the method returns, and the method itself will return a particular value or throw a particular an exception. This kind of description divides naturally into a precondition (describing the object s state before invoking the method) and a postcondition, describing the object s state and return value after the method returns. For example, if the firstin-first-out (FIFO) queue object is non-empty (precondition), then the (deq() method will remove and return the first element (postcondition), and otherwise it will throw an exception (another pre- and postcondition). This style of documentation is so familiar that it is easy to overlook how elegant and powerful it is. The size of the object s documentation is linear in the number of methods, because each method can be described in isolation. The 0 This chapter is part of the Manuscript Multiprocessor Synchronization by Maurice Herlihy and Nir Shavit copyright c 2003, all rights reserved. 1

2 CHAPTER 3. CONCURRENT OBJECTS AND LINEARIZABILITY vast number of potential interactions among methods are captured implicitly through their side-effects on the object state. The object has a well-defined state between method calls (for example, a FIFO queue is a sequence of items). The object s documentation describes the object state before and after each call, and we can safely ignore any intermediate states the object may assume while the method call is in progress. Defining objects in terms of preconditions and postconditions makes perfect sense in a sequential model computation where a single thread manipulates a collection of objects. For objects shared by multiple threads, this successful and familiar style of documentation falls apart. If an object s methods can be invoked by concurrent threads, then the method executions can overlap in time, and it no longer makes sense to talk about the order of method calls. What does it mean, in a multithreaded program, if x and y are enqueued on a FIFO queue during overlapping intervals? Which will be dequeued first? Can we continue to describe methods in isolation, via pre- and post-conditions, or must we provide explicit descriptions of every possible interaction among every possible collection of concurrent method calls? Even the notion of an object s state becomes confusing. In single-thread programs, an object needs to assume a meaningful state only between method calls. For concurrent objects, however, overlapping method calls may be in progress at every instant, thus the object may never be between method calls. When implementing such a method, one must be prepared to encounter an object state that reflects the incomplete effects of concurrent method calls, a problem that just does not arise in single-threaded programs. 3.2 Linearizability We propose the following way out of this dilemma, which we call The Linearizability Manifesto. Each method call should appear to take effect instantaneously at some moment between its invocation and response. This manifesto is not a scientific statement, since it cannot be proved right or wrong. Nevertheless, since it was proposed in 1990, this property (called linearizability) has achieved widespread acceptance. There are certain specialized situations where other correctness properties are more appropriate, but for now they are the exception, not the rule. One immediate advantage of linearizability is that there is no need to describe vast numbers of interactions among concurrent method calls, because we can still use the familiar and well-understood pre- and post-condition style of describing object methods. True, concurrency introduces additional uncertainty (what else did you expect?). If x and y are enqueued on an empty FIFO queue during overlapping intervals, then one method call will take effect before the other, so a later dequeue will return either x or y (either is correct), but it will not return some z, or throw an exception.

3.3. EXAMPLES 3 Figure 3.1: History H 1 Linearizability is a local property: a system is linearizable if and only if each individual object is linearizable. Locality enhances modularity and concurrency, since objects can be implemented and verified independently, and run-time scheduling can be completely decentralized. Linearizability is also a non-blocking property: one method is never forced to wait to synchronize with another. Of course, a particular object implementation may force one method to wait for another, but such waiting is a product of the implementation, not forced by the correctness condition. (Methods may also wait for logical reasons unrelated to synchronization. For example, a dequeue applied to an empty queue may wait for another thread to enqueue something.) Properties such as locality and being non-blocking may seem simple, but they should not be taken for granted. We will see that many other notions of concurrent correctness have been proposed that lack these important properties. 3.3 Examples To reinforce out intution about linearizable executions, we will review a number of simple examples, mostly based on FIFO queues. Method calls take time. An operation is the interval that starts with a method invocation event and ends with a method response event. Here is a key point. When we say that an operation takes effect instantaneously, we mean that we effectively reorder operations so that they occur sequentially, that is, methods are called, they execute, and they return, one at a time without overlapping. When we say that an operation takes effect between the method invocation and response, we meant that two operations that do not overlap must be ordered in that order (or, more formally, in an order consistent with their real-time precedence). Overlapping method calls can be ordered either way.

4 CHAPTER 3. CONCURRENT OBJECTS AND LINEARIZABILITY Figure 3.2: History H 2 Figure 3.3: History H 3

3.3. EXAMPLES 5 Figure 3.4: History H 4 Figure 3.5: History H 5

6 CHAPTER 3. CONCURRENT OBJECTS AND LINEARIZABILITY Let us examine some ways in which a concurrent FIFO queue might behave. Our figures are based on the following conventions. Time moves from left to right, and arrows indicate intervals. Each thread s intervals are displayed along a horizontal line. The thread name appears on the left. A double-headed arrow represents an interval with a fixed start time and stop time. A single-headed arrow represents an interval with a fixed start time and an unknown stop time. The label q.enq(a)a means that thread A enqueues item a at object q, while q.deq(a)a means that thread A dequeues item a from object q. In H 1 (Figure 3.1), thread A and B concurrently enqueue items x and y. Later, B dequeues x, and then A dequeues y. Since the dequeue interval for x precedes the dequeue inteval for y, their enqueues must have taken effect in that order. In fact, their enqueues were concurrent, thus they could indeed have taken effect in that order. By contrast, the behavior shown in H 2 (Figure 3.2) is not linearizable. Here, x must have been enqueued before y (their intervals do not overlap), yet y is dequeued without x having been dequeued. To be linerizable, A should have dequeued x. H 3 (Figure 3.3) is linerarizable, even though x is dequeued before its enqueuing inteval is complete. Intuitively, the enqueue of x took effect before it completed. H 4 (Figure 3.4) is not linearizable because y is dequeued twice. Linearizable behavior can also be defined for other concurrent data structures such as read/write registers, stacks, sets, directories, and so on. Figures 3.5 and 3.6 show histories H 5 and H 6 for a Register object. In both, threads B and C write concurrently, but A s read (of 1) implies that B s write (of 1) takes effect before C s write (of 0). H 5 is linearizable because B s final read (of 1) is consistent with this order, while H 6 is not, because B s final read (of 0) is inconsistent. 3.4 Queue Implementations Figure 3.7 shows a standard Java implementation of a shared FIFO queue. Figure?? shows an execution in which A enqueues a, B enqueues b, and C dequeues b. Overlapping intervals indicate concurrent operations. All three method calls overlap in time. The time arrow shows which thread holds the lock. (For simplicity, we consider lock entry and exit intervals to be instantaneous.) Here, C acquires the lock, observes the queue to be empty, and releases the lock. B acquires the lock, inserts b, and releases the lock. A acquires the lock, inserts a, and releases the lock. C reacquies the lock, dequeues b, releases the lock and returns. With so much operation overlapping and acquiring, releasing, and reacquiring of locks, how can we tell whether this object is linearizable? In fact, this particular execution is indeed linearizable because the actual updates to the object occur sequentially, in distinct critical sections, even though the method calls overlap. Thread C s opeation takes effect the second time it acquires the lock, while the other threads operations take effect the first (and

3.4. QUEUE IMPLEMENTATIONS 7 class Queue { int head = 0; int tail = 0; Item[QSIZE] items; // next item to dequeue // next empty slot public synchronized void enq(item x) { while (this.tail - this.head == QSIZE) { try { this.wait(); // wait until not full catch (InterruptedException e) {; // ignore exceptions this.items[this.tail++ % QSIZE] = x; this.notifyall(); public synchronized Item deq() { while (this.tail - this.head == 0) { try { this.wait(); // wait until non-empty catch (InterruptedException e) {; // ignore exceptions Item x = this.items[this.head++]; this.notifyall(); return x; Figure 3.7: Lock-based FIFO Queue class Queue { int head = 0; int tail = 0; Item[QSIZE] items; // next item to dequeue // next empty slot public void enq(item x) { while (this.tail - this.head == QSIZE); // busy-wait this.items[this.tail % QSIZE] = x; this.tail++; public Item deq() { while (this.tail == this.head); // busy-wait Item x = this.items[this.head % QSIZE]; this.head++; return x; Figure 3.8: FIFO Queue without Mutual Exclusion

8 CHAPTER 3. CONCURRENT OBJECTS AND LINEARIZABILITY Figure 3.9: Locking queue execution

3.5. PRECISE DEFINITIONS 9 only) time they acquire the lock. This example suggests one approach: can we identify an instant where each operation takes effect? Figure 3.8 shows an alternative queue implementation, intended to be shared by two threads only, that does not use mutual exclusion. This implementation is also linearizable. We will not give a formal proof, but if you think about it, you may see that each enq() operation takes effect when the tail field is updated, and each (deq() operation takes effect when the head field is updated. Here, instead of identifying the coarse-grained critical section in which the operation takes effect, we identify the fine-grained field update where it takes effect. This approach of identifying the atomic step where an operation takes effect is the most common way to show that an implementation is linearizable. There are special cases where this approach does not work, but they are rare, and you are unlikely to encounter them. 3.5 Precise Definitions Informally, we know that a concurrent object is linearizable if each operation appears to take effect instantaneously at some moment between that method s invocation and return events. This statement is probably enough for most informal reasoning, but a more precise formulation is needed to take care of some tricky cases (like operations that haven t returned), and for more rigorous styles of argument. An execution of a concurrent system is modeled by a history, which is a finite sequence of method invocation and response events. A subhistory of a history H is a subsequence of the events of H. A method invocation is written as < x.m(args )A >, where x is an object, m a method name, args a sequence of arguments values, and A a thread. The invocation s response is written as < x : term(res )A > where term is a termination condition and res is a sequence of results. We use Ok for normal termination. Definition A response matches an invocation if their objects names agree and their thread names agree. Definition An invocation is pending in a history if no matching response follows the invocation. Definition If H is a history, complete(h) is the maximal subsequence of H consisting only of invocations and matching responses. Definition A history H is sequential if (1) The first event of H is an invocation. (2) Each invocation, except possibly the last, is immediately followed by a matching response. A history that is not sequential is concurrent. Definition A thread subhistory, H P (H at P ), of a history H is the subsequence of all events in H whose thread names are P. (An object subhistory H x

10 CHAPTER 3. CONCURRENT OBJECTS AND LINEARIZABILITY is similarly defined for an object x.) Definition Two histories H and H are equivalent if for every thread A, H A = H A. Definition A history H is well-formed if each thread subhistory H A of H is sequential. All histories considered here are well-formed. Notice that thread subhistories of a well-formed history are always sequential, but object subhistories need not be. An operation is a pair consisting of an invocation x.m(a)a, and the next matching response x : t(r)a. A set S of histories is prefix-closed if whenever H is in S, every prefix of H is also in S. A sequential specification for an object is a prefix-closed set of sequential histories for the object. A sequential history H is legal if each object subhistory H x belongs to the sequential specification for x. An method is total if it is defined for every object state, otherwise it is partial. For example, consider the following specification for an unbounded sequential FIFO queue. one can always enqueue another item, but one can dequeue only from a non-empty queue. In this specification, method enq() is total, since its effects are defined in every queue state. Method (deq(), however, is partial, since its effects are defined only for non-empty queues. A history H induces an irreflexive partial order H on methods: e 1 H e 2 if res(e 1 ) precedes inv(e 2 ) in H. If H is sequential, then H is a total order. 3.5.1 Linearizability Definition A history H is linearizable if it can be extended (by appending zero or more response events) to a history H such that: L1 : complete(h ) is equivalent to some legal sequential history S, and L2 : H S. Informally, extending H to H captures the idea that some pending invocations may have taken effect even though their responses have not yet been returned to the caller. Extending H to H while restricting attention to complete(h ) makes it possible to complete pending methods, or just to ignore them. L2 states that this apparent sequential interleaving respects the precedence ordering of methods. 3.6 Properties of Linearizability Theorem 3.6.1 H is linearizable and only if for each object x, H x is linearizable.

3.6. PROPERTIES OF LINEARIZABILITY 11 P E(x) A P E(y) B P D(y) B (a) P E(y) B P D(y) B P E(x) A (b) Figure 3.10: History H 7 and its equivalent sequential history. This property of linearizability is called locality. Locality is important because it allows concurrent systems to be designed and constructed in a modular fashion; linearizable objects can be implemented, verified, and executed independently. Locality should not be taken for granted; as the following discussion shows, the literature includes proposals for alternative correctness properties that are not local. Definition [Lamport] A history is sequentially consistent if and only if it is equivalent to a legal sequential history. This condition is weaker than linearizability since it only requires condition L1 of the definition of the linearizability. Figure?? shows a sequentailly consistent FIFO queue history. The history H 8 for objects p and q, shown in Figure??, shows that sequential consistency is not a local property. H 8 p is sequentially consistent (reorder op1 and op2). H 8 q is also sequentially consisten (reorder op3 and op4). H 8 itself is not sequentially consistent, because there is no total order consistent with both subhistories. 3.6.1 Comparison to Other Correctness Conditions Much work on databases and distributed systems uses serializability as the basic correctness condition for concurrent computations. In this model, a transaction is a thread of control that applies a finite sequence of methods to a set of objects shared with other transactions. Definition A history is serializable if it is equivalent to one in which transactions appear to execute sequentially, that is, without interleaving. Definition A history is strictly serializable if the transactions order in the sequential history is compatible with their precedence order: if every method

12 CHAPTER 3. CONCURRENT OBJECTS AND LINEARIZABILITY H8 : p E(x) A q E(y) B q E(x) A p E(y) B p D(y) A q D(x) B (a) H8 p : E(x) A op1 E(y) B op2 D(y) A H8 q : E(y) B op3 E(x) A op4 D(x) B (b) Figure 3.11: History H 8. call of one transaction preceeds every method call of another, the former is serialized first. Linearizability can be viewed as a special case of strict serializability where transactions are restricted to consist of a single method applied to a single object. Nevertheless, this single-operation restriction has far-reaching consequences. Neither serializability nor strict serializability is a local property. For example, in history H 8 shown above, if we interpret A and B as transactions instead of threads, then it is easily seen that both H p A and H 8 q are strictly serializable but H 8 is not. (Because A and B overlap at each object, they are unrelated by transaction precedence in either subhistory.) Moreover, since A and B each dequeues an item enqueued by the other, H 8 is not even serializable. A practical consequence of this observation is that implementors of objects in serializable systems must rely on convention to ensure that all objects concurrency control mechanisms are compatible with one another. Another important difference between linearizability and serializability is that both serializability and strict serializability are blocking properties, while linearizability is non-blocking. Under certain circumstances, a transaction may be unable to complete a pending method without violating serializability. Such a transaction must be rolled back and restarted, implying that additional mechanism must be provided for that purpose. For example, consider the history H 9, involving two register objects x and y, and two transactions A and B. A and B respectively read x and y and then attempt to write new values to y and x. It is easy to see that both pending invocations cannot be completed without violating serializability. Perhaps the major distinction between serializability and linearizability is

3.6. PROPERTIES OF LINEARIZABILITY 13 x R(0) A y R(0) B x W(1) B y W(1) A Figure 3.12: History H9 that the two notions are appropriate for different problem domains. Serializability is appropriate for database systems, in which it must be easy for application programmers to preserve complex application-specific invariants spanning multiple objects. Linearizability is appropriate for generalized individual shared object implementations.