Synchronization COMPSCI 386
Obvious? // push an item onto the stack while (top == SIZE) ; stack[top++] = item; // pop an item off the stack while (top == 0) ; item = stack[top--]; PRODUCER CONSUMER Suppose top is 5. What will its value be after one push and one pop operation?
Race Conditions register = top register = register + 1 top = register top++ register = top register = register 1 top = register top-- Machine instructions may be interleaved in such a way that top is 4, 5, or 6 after one push and one pop.
Race Conditions A race condition arises when multiple threads access the same data and the result depends on the order in which the threads are scheduled. The kernel manages various data structures like the file desriptor table and the ready queue, so it must be carefully designed to avoid race conditions. Easy solution: non-preemptive kernel. But most kernels are preemptive (e.g., Linux 2.6+) for responsiveness (and for real-time systems).
Critical Section Problem A critical section is a segment of code in which a process may be accessing or modifying resources shared with other processes. The critical section problem is to design a protocol for access to critial section code in order to prevent race conditions. Required properties of a solution: mutual exclusion progress bounded waiting
Peterson s Solution (1981) Software solution that does not really work because of the way modern systems perform basic machine language instructions like load and store (caching). But it provides a good conceptual starting point for the study of the critical section problem. Works for two processes sharing a single resource. Can be generalized, but becomes more complicated.
Peterson s Solution (1981) do { interested[i] = true; turn = j; while (interested[j] && turn == j) ; entry section for P i CRITICAL SECTION interested[i] = false; exit section for P i } while (true);
Classic Synchronization Problems Illustrate many common concurrency problems. Used for testing new synchronization schemes. Bounded Buffer Readers-Writers Dining Philosphers
Synchronization Objects A mutex lock guarantees mutual exclusion. Spin locks are suitable when resource will be held for short period. A semaphore is a counter that can be atomically incremented and decremented across a prescribed range of values. Initialized to number of instances of a sharable resource. A mutex is simply a binary semaphore.
Synchronization Objects A monitor is a software abstraction that hides some of the notoriously tricky details involved in the use of mutex locks and semaphores. Like a locked room for execution of critical section code. Consider a Java a class with methods for entering/exiting the critical section. Hardware support for synchonization atomic test-and-set instruction atomic compare-and-swap instructions
Synchronization in Linux Using machine instructions for atomic arithmetic operations, provides an opaque type atomic_t. atomic_t counter; atomic_set(&counter, 5); atomic_inc(&counter); atomic_add(10, &counter); atomic_sub(4, &counter); int x = atomic_read(&counter); Also provides mutex locks, spinlocks, semaphores.
Java Prior to 5.0 Every object has a built-in lock that can be acquired by calling a synchronized method. Every class has a lock that can be aquired by calling a static synchronized method. Blocks can be synchronized. If one thread owns a lock and another tries to obtain it, the latter sleeps; when owner releases the lock, the JVM awakens one of the sleeping threads.
Java Prior to 5.0 Every object has a wait set of threads. When wait is called the lock is released, the thread blocks, and is placed in the wait set for the object. When notify is called, the JVM selects a thread from the wait set, moves it to the entry set, and sets its state to runnable.
Java 5.0 and Beyond The concurrency API includes: Lock interface. Semaphore class. Condition class. Much more. Packages: java.util.concurrent.atomic java.util.concurrent.locks