The Synchronization Toolbox

Similar documents
Synchronization: Going Deeper

Threads and Concurrency

Lecture 6 (cont.): Semaphores and Monitors

CSE 120 Principles of Operating Systems Spring 2016

Lecture 5: Synchronization w/locks

Synchronization. Dr. Yingwu Zhu

CSE 153 Design of Operating Systems

CS 162 Operating Systems and Systems Programming Professor: Anthony D. Joseph Spring Lecture 8: Semaphores, Monitors, & Condition Variables

Concurrency and Synchronisation

Concurrency and Synchronisation

Introduction to OS Synchronization MOS 2.3

Learning Outcomes. Concurrency and Synchronisation. Textbook. Concurrency Example. Inter- Thread and Process Communication. Sections & 2.

Lecture 7: CVs & Scheduling

Lecture 8: September 30

CSE 120 Principles of Operating Systems Spring 2016

What's wrong with Semaphores?

Synchronization COMPSCI 386

Dealing with Issues for Interprocess Communication

Operating Systems. Operating Systems Summer 2017 Sina Meraji U of T

CSE 451: Operating Systems Winter Lecture 7 Synchronization. Steve Gribble. Synchronization. Threads cooperate in multithreaded programs

CS 153 Design of Operating Systems Winter 2016

Synchronization I. Jo, Heeseung

Concurrency Control. Synchronization. Brief Preview of Scheduling. Motivating Example. Motivating Example (Cont d) Interleaved Schedules

Last Class: Synchronization. Review. Semaphores. Today: Semaphores. MLFQ CPU scheduler. What is test & set?

CSE 120 Principles of Operating Systems Spring 2016

Synchronization 1. Synchronization

CSE 451: Operating Systems Winter Lecture 7 Synchronization. Hank Levy 412 Sieg Hall

Synchronization. CISC3595/5595 Fall 2015 Fordham Univ.

10/17/ Gribble, Lazowska, Levy, Zahorjan 2. 10/17/ Gribble, Lazowska, Levy, Zahorjan 4

Operating Systems ECE344

CS 153 Design of Operating Systems Winter 2016

CS 318 Principles of Operating Systems

Opera&ng Systems ECE344

Deadlock and Monitors. CS439: Principles of Computer Systems September 24, 2018

Concurrency: a crash course

CS533 Concepts of Operating Systems. Jonathan Walpole

Models of concurrency & synchronization algorithms

Operating Systems. Synchronization

Background. The Critical-Section Problem Synchronisation Hardware Inefficient Spinning Semaphores Semaphore Examples Scheduling.

CSE 120. Fall Lecture 6: Semaphores. Keith Marzullo

CSE 153 Design of Operating Systems Fall 2018

CHAPTER 6: PROCESS SYNCHRONIZATION

Synchronization I. Jin-Soo Kim Computer Systems Laboratory Sungkyunkwan University

Synchronization for Concurrent Tasks

Lesson 6: Process Synchronization

The University of Texas at Arlington

Chapter 6: Process Synchronization. Operating System Concepts 8 th Edition,

CS 318 Principles of Operating Systems

Background. Old Producer Process Code. Improving the Bounded Buffer. Old Consumer Process Code

CS 333 Introduction to Operating Systems. Class 4 Concurrent Programming and Synchronization Primitives

Synchronization 1. Synchronization

Concurrency and Synchronisation. Leonid Ryzhyk

The University of Texas at Arlington

CSE 153 Design of Operating Systems

Deadlock and Monitors. CS439: Principles of Computer Systems February 7, 2018

Chapter 5: Process Synchronization. Operating System Concepts Essentials 2 nd Edition

CS 31: Introduction to Computer Systems : Threads & Synchronization April 16-18, 2019

Synchronization. CSE 2431: Introduction to Operating Systems Reading: Chapter 5, [OSC] (except Section 5.10)

High-level Synchronization

Process Management And Synchronization

Concurrency. Chapter 5

Module 6: Process Synchronization. Operating System Concepts with Java 8 th Edition

IT 540 Operating Systems ECE519 Advanced Operating Systems

CS 318 Principles of Operating Systems

Outline Part 2. Semaphores. Semaphore Usage. Objectives: Administrative details:

Operating Systems, Assignment 2 Threads and Synchronization

Semaphores and Monitors: High-level Synchronization Constructs

Synchronization. CS 416: Operating Systems Design, Spring 2011 Department of Computer Science Rutgers University

Semaphores. Jinkyu Jeong Computer Systems Laboratory Sungkyunkwan University

Chapter 6: Synchronization. Chapter 6: Synchronization. 6.1 Background. Part Three - Process Coordination. Consumer. Producer. 6.

G52CON: Concepts of Concurrency

Chapter 6: Process Synchronization

Chapter 5: Process Synchronization. Operating System Concepts 9 th Edition

SYNCHRONIZATION M O D E R N O P E R A T I N G S Y S T E M S R E A D 2. 3 E X C E P T A N D S P R I N G 2018

Chapter 6: Process Synchronization

Chapter 5: Process Synchronization. Operating System Concepts 9 th Edition

Page 1. Goals for Today" Atomic Read-Modify-Write instructions" Examples of Read-Modify-Write "

Concurrent Processes Rab Nawaz Jadoon

Synchroniza+on II COMS W4118

Concurrency: Signaling and Condition Variables

Page 1. Goals for Today" Atomic Read-Modify-Write instructions" Examples of Read-Modify-Write "

CS 162 Operating Systems and Systems Programming Professor: Anthony D. Joseph Spring 2004

CSE 120 Principles of Operating Systems

CPS 310 second midterm exam, 11/6/2013

Locks. Dongkun Shin, SKKU

CPS 110 Midterm. Spring 2011

Operating Systems CMPSCI 377 Spring Mark Corner University of Massachusetts Amherst

Chapter 6: Process Synchronization

Page 1. Goals for Today. Atomic Read-Modify-Write instructions. Examples of Read-Modify-Write

Operating Systems. Designed and Presented by Dr. Ayman Elshenawy Elsefy

Concurrency. On multiprocessors, several threads can execute simultaneously, one on each processor.

February 23 rd, 2015 Prof. John Kubiatowicz

Threads and Parallelism in Java

CMSC 330: Organization of Programming Languages

Concurrency, Thread. Dongkun Shin, SKKU

Synchronization. CS61, Lecture 18. Prof. Stephen Chong November 3, 2011

Synchronization II: EventBarrier, Monitor, and a Semaphore. COMPSCI210 Recitation 4th Mar 2013 Vamsi Thummala

Concurrency. On multiprocessors, several threads can execute simultaneously, one on each processor.

C09: Process Synchronization

Recap: Thread. What is it? What does it need (thread private)? What for? How to implement? Independent flow of control. Stack

Transcription:

The Synchronization Toolbox

Mutual Exclusion Race conditions can be avoiding by ensuring mutual exclusion in critical sections. Critical sections are code sequences that are vulnerable to races. Every race (possible incorrect interleaving) involves two or more threads executing related critical sections concurrently. To avoid races, we must serialize related critical sections. Never allow more than one thread in a critical section at a time. 1. BAD 2. interrupted critsec BAD 3. GOOD

Locks Locks can be used to ensure mutual exclusion in conflicting critical sections. A lock is an object, a data item in memory. Methods: Lock::Acquire and Lock::Release. Threads pair calls to Acquire and Release. Acquire before entering a critical section. Release after leaving a critical section. Between Acquire/Release, the lock is held. Acquire does not return until any previous holder releases. Waiting locks can spin (a spinlock) or block (a mutex). A R A R

Example: Per-Thread Counts and Total /* shared by all threads */ int counters[n]; int total; /* * Increment a counter by a specified value, and keep a running sum. * This is called repeatedly by each of N threads. * tid is an integer thread identifier for the current thread. * value is just some arbitrary number. */ void TouchCount(int tid, int value) { counters[tid] += value; total += value;

Using Locks: An Example int counters[n]; int total; Lock *lock; /* * Increment a counter by a specified value, and keep a running sum. */ void TouchCount(int tid, int value) { lock->acquire(); counters[tid] += value; /* critical section code is atomic...*/ total += value; /* as long as the lock is held */ lock->release();

Reading Between the Lines of C load add store load add store vulnerable between load and store of counters[tid]...but it s non-shared. vulnerable between load and store of total, which is shared. /* counters[tid] += value; total += value; */ load counters, R1 ; load counters base load 8(SP), R2 ; load tid index shl R2, #2, R2 ; index = index * sizeof(int) add R1, R2, R1 ; compute index to array load 4(SP), R3 ; load value load (R1), R2 ; load counters[tid] add R2, R3, R2 ; counters[tid] += value store R2, (R1) ; store back to counters[tid] load total, R2 ; load total add R2, R3, R2 ; total += value store R2, total ; store total

Portrait of a Lock in Motion R A A R

Condition Variables Condition variables allow explicit event notification. much like a souped-up sleep/wakeup associated with a mutex to avoid sleep/wakeup races Condition::Wait(Lock*) Called with lock held: sleep, atomically releasing lock. Atomically reacquire lock before returning. Condition:: Signal(Lock*) Wake up one waiter, if any. Condition::Broadcast(Lock*) Wake up all waiters, if any.

A New Synchronization Problem: Ping-Pong Pong void PingPong() { while(not done) { if (blue) switch to purple; if (purple) switch to blue; How to do this correctly using sleep/wakeup? How to do it without using sleep/wakeup?

Ping-Pong Pong with Sleep/Wakeup? void PingPong() { while(not done) { blue->sleep(); purple->wakeup(); void PingPong() { while(not done) { blue->wakeup(); purple->sleep();

Ping-Pong Pong with Mutexes? void PingPong() { while(not done) { Mx->Acquire(); Mx->Release();

Mutexes Don t Work for Ping-Pong Pong

Ping-Pong Pong Using Condition Variables void PingPong() { mx->acquire(); while(not done) { cv->signal(); cv->wait(); mx->release(); See how the associated mutex avoids sleep/wakeup races?

Bounded Resource with a Condition Variable Mutex* mx; Condition *cv; int AllocateEntry() { int i; mx->acquire(); while(!findfreeitem(&i)) cv.wait(mx); slot[i] = 1; mx->release(); return(i); Loop before you leap. Why is this Acquire needed? void ReleaseEntry(int i) { mx->acquire(); slot[i] = 0; cv->signal(); mx->release();

Semaphores using Condition Variables void Down() { mutex->acquire(); ASSERT(count >= 0); while(count == 0) condition->wait(mutex); count = count - 1; mutex->release(); void Up() { mutex->acquire(); count = count + 1; condition->signal(mutex); mutex->release(); (Loop before you leap!) This constitutes a proof that mutexes and condition variables are at least as powerful as semaphores.

Semaphores Semaphores handle all of your synchronization needs with one elegant but confusing abstraction. controls allocation of a resource with multiple instances a non-negative integer with special operations and properties initialize to arbitrary value with Init operation souped up increment (Up or V) and decrement (Down or P) atomic sleep/wakeup behavior implicit in P and V P does an atomic sleep, if the semaphore value is zero. P means probe ; it cannot decrement until the semaphore is positive. V does an atomic wakeup. num(p) <= num(v) + init

A Bounded Resource with a Counting Semaphore semaphore->init(n); int AllocateEntry() { int i; semaphore->down(); ASSERT(FindFreeItem(&i)); slot[i] = 1; return(i); A semaphore for an N-way resource is called a counting semaphore. A caller that gets past a Down is guaranteed that a resource instance is reserved for it. Problems? void ReleaseEntry(int i) { slot[i] = 0; semaphore->up(); Note: the current value of the semaphore is the number of resource instances free to allocate. But semaphores do not allow a thread to read this value directly. Why not?

The Roots of Condition Variables: Monitors A monitor is a module (a collection of procedures) in which execution is serialized. CVs are easier to understand if we think about them in terms of the original monitor formulation. ready to enter blocked (enter) signal() wait() state P1() P2() P3() P4() [Brinch Hansen 1973, C.A.R. Hoare 1974] At most one thread may be active in the monitor at a time. (exit) A thread may wait in the monitor, allowing another thread to enter. A thread in the monitor may signal a waiting thread, causing it to return from its wait and reenter the monitor.

Hoare Semantics Suppose purple signals blue in the previous example. Hoare semantics: the signaled thread immediately takes over the monitor, and the signaler is suspended. ready to enter (enter) signal() (Hoare) state P1() P2() P3() P4() (exit) signal() (Hoare) suspended The signaler does not continue in the monitor until the signaled thread exits or waits again. waiting wait() Hoare semantics allow the signaled thread to assume that the state has not changed since the signal that woke it up.

Mesa Semantics Suppose again that purple signals blue in the original example. ready to (re)enter Mesa semantics: the signaled thread transitions back to the ready state. waiting signal() (Mesa) (enter) wait() state P1() P2() P3() P4() (exit) There is no suspended state: the signaler continues until it exits the monitor or waits. The signaled thread contends with other ready threads to (re)enter the monitor and return from wait. Mesa semantics are easier to understand and implement... BUT: the signaled thread must examine the monitor state again after the wait, as the state may have changed since the signal. Loop before you leap!

From Monitors to Mx/Cv Pairs Mutexes and condition variables (as in Nachos) are based on monitors, but they are more flexible. A monitor is just like a module whose state includes a mutex and a condition variable. It s just as if the module s methods Acquire the mutex on entry and Release the mutex before returning. But with mutexes, the critical regions within the methods can be defined at a finer grain, to allow more concurrency. With condition variables, the module methods may wait and signal on multiple independent conditions. Nachos (and Topaz and Java) use Mesa semantics for their condition variables: loop before you leap!

Mutual Exclusion in Java Mutexes and condition variables are built in to every Java object. no explicit classes for mutuxes and condition variables Every object is/has a monitor. At most one thread may own any given object s monitor. A thread becomes the owner of an object s monitor by executing a method declared as synchronized some methods may choose not to enforce mutual exclusion (unsynchronized) by executing the body of a synchronized statement supports finer-grained locking than pure monitors allow exactly identical to the Modula-2 LOCK(m) DO construct in Birrell

Wait/Notify in Java Every Java object may be treated as a condition variable for threads using its monitor. public class Object { void notify(); /* signal */ void notifyall(); /* broadcast */ void wait(); void wait(long timeout); public class PingPong (extends Object) { public synchronized void PingPong() { while(true) { notify(); wait(); A thread must own an object s monitor to call wait/notify, else the method raises an IllegalMonitorStateException. Wait(*) waits until the timeout elapses or another thread notifies, then it waits some more until it can re-obtain ownership of the monitor: Mesa semantics. Loop before you leap!

What to Know about Sleep/Wakeup 1. Sleep/wakeup primitives are the fundamental basis for all blocking synchronization. 2. All use of sleep/wakeup requires some additional low-level mechanism to avoid missed and double wakeups. disabling interrupts, and/or constraints on preemption, and/or spin-waiting (Unix kernels use this instead of disabling interrupts) (on a multiprocessor) 3. These low-level mechanisms are tricky and error-prone. 4. High-level synchronization primitives take care of the details of using sleep/wakeup, hiding them from the caller. semaphores, mutexes, condition variables

Semaphores as Mutexes semapohore->init(1); void Lock::Acquire() { semaphore->down(); void Lock::Release() { semaphore->up(); Semaphores must be initialized with a value representing the number of free resources: mutexes are a single-use resource. Down() to acquire a resource; blocks if no resource is available. Up() to release a resource; wakes up one waiter, if any. Up and Down are atomic. Mutexes are often called binary semaphores. However, real mutexes have additional constraints on their use.

Spin-Yield: Just Say No void Thread::Await() { awaiting = TRUE; while(awaiting) Yield(); void Thread::Awake() { if (awaiting) awaiting = FALSE;

The Magic of Semaphores and CVs Any use of sleep/wakeup synchronization can be replaced with semaphores or condition variables. Most uses of blocking synchronization have some associated state to record the blocking condition. e.g., list or count of waiting threads, or a table or count of free resources, or the completion status of some operation, or... The trouble with sleep/wakeup is that the program must update the state atomically with the sleep/wakeup. Semaphores integrate the state into atomic P/V primitives....but the only state that is supported is a simple counter. Condition variables (CVs) allow the program to define the condition/state, and protect it with an integrated mutex.

Semaphores vs. Condition Variables 1. Up differs from Signal in that: Signal has no effect if no thread is waiting on the condition. Condition variables are not variables! They have no value! Up has the same effect whether or not a thread is waiting. Semaphores retain a memory of calls to Up. 2. Down differs from Wait in that: Down checks the condition and blocks only if necessary. no need to recheck the condition after returning from Down wait condition is defined internally, but is limited to a counter Wait is explicit: it does not check the condition, ever. condition is defined externally and protected by integrated mutex

Guidelines for Choosing Lock Granularity 1. Keep critical sections short. Push noncritical statements outside of critical sections to reduce contention. 2. Limit lock overhead. Keep to a minimum the number of times mutexes are acquired and released. Note tradeoff between contention and lock overhead. 3. Use as few mutexes as possible, but no fewer. Choose lock scope carefully: if the operations on two different data structures can be separated, it may be more efficient to synchronize those structures with separate locks. Add new locks only as needed to reduce contention. Correctness first, performance second!

int initialized = 0; Lock initmx; Tricks of the Trade #1 void Init() { InitThis(); InitThat(); initialized = 1; void DoSomething() { if (!initialized) { /* fast unsynchronized read of a WORM datum */ initmx.lock(); /* gives us a hint that we re in a race to write */ if (!initialized) /* have to check again while holding the lock */ Init(); initmx.unlock(); /* slow, safe path */ DoThis(); DoThat();

Things Your Mother Warned You About #1 Lock dirtylock; List dirtylist; Lock wiredlock; List wiredlist; #define WIRED #define DIRTY #define FREE 0x1 0x2 0x4 struct buffer { unsigned int flags; struct OtherStuff etc; ; void MarkDirty(buffer* b) { dirtylock.acquire(); b->flags = DIRTY; dirtylist.append(b); dirtylock.release(); void MarkWired(buffer *b) { wiredlock.acquire(); b->flags = WIRED; wiredlist.append(b); wiredlock.release();

More Locking Guidelines 1. Write code whose correctness is obvious. 2. Strive for symmetry. Show the Acquire/Release pairs. Factor locking out of interfaces. Acquire and Release at the same layer in your layer cake of abstractions and functions. 3. Hide locks behind interfaces. 4. Avoid nested locks. If you must have them, try to impose a strict order. 5. Sleep high; lock low. Design choice: where in the layer cake should you put your locks?

Guidelines for Condition Variables 1. Understand/document the condition(s) associated with each CV. What are the waiters waiting for? When can a waiter expect a signal? 2. Always check the condition to detect spurious wakeups after returning from a wait: loop before you leap! Another thread may beat you to the mutex. The signaler may be careless. A single condition variable may have multiple conditions. 3. Don t forget: signals on condition variables do not stack! A signal will be lost if nobody is waiting: always check the wait condition before calling wait.

Stuff to Know Know how to use mutexes, CVs, and semaphores. It is a craft. Learn to think like Birrell: write concurrent code that is clean and obviously correct, and balances performance with simplicity. Understand why these abstractions are needed: sleep/wakeup races, missed wakeup, double wakeup, interleavings, critical sections, the adversarial scheduler, multiprocessors, thread interactions, ping-pong. Understand the variants of the abstractions: Mesa vs. Hoare semantics, monitors vs. mutexes, binary semaphores vs. counting semaphores, spinlocks vs. blocking locks. Understand the contexts in which these primitives are needed, and how those contexts are different: processes or threads in the kernel, interrupts, threads in a user program, servers, architectural assumptions. Where should we define/implement synchronization abstractions? Kernel? Library? Language/compiler? Reflect on scheduling issues associated with synchronization abstractions: how much should a good program constrain the scheduler? How much should it assume about the scheduling semantics of the primitives?