Operating System Labs. Yuanbin Wu

Similar documents
Locks. Dongkun Shin, SKKU

Condition Variables. Dongkun Shin, SKKU

[537] Locks and Condition Variables. Tyler Harter

Condition Variables. Dongkun Shin, SKKU

Locks and semaphores. Johan Montelius KTH

recap, what s the problem Locks and semaphores Total Store Order Peterson s algorithm Johan Montelius 0 0 a = 1 b = 1 read b read a

TCSS 422: OPERATING SYSTEMS

Synchronization I. To do

Locks and semaphores. Johan Montelius KTH

Synchronization II. q Condition Variables q Semaphores and monitors q Some classical problems q Next time: Deadlocks

Synchronization I. Today. Next time. Race condition, critical regions and locks. Condition variables, semaphores and Monitors

Synchronization I. Today. Next time. Monitors. ! Race condition, critical regions and locks. ! Condition variables, semaphores and

Condition Variables. Jinkyu Jeong Computer Systems Laboratory Sungkyunkwan University

28. Locks. Operating System: Three Easy Pieces

Condition Variables. Figure 29.1: A Parent Waiting For Its Child

Interlude: Thread API

TCSS 422: OPERATING SYSTEMS

Implementing Locks. Nima Honarmand (Based on slides by Prof. Andrea Arpaci-Dusseau)

Condition Variables & Semaphores

Synchronization II. Today. ! Condition Variables! Semaphores! Monitors! and some classical problems Next time. ! Deadlocks

Lecture 4. Threads vs. Processes. fork() Threads. Pthreads. Threads in C. Thread Programming January 21, 2005

Concurrency, Thread. Dongkun Shin, SKKU

CIS Operating Systems Application of Semaphores. Professor Qiang Zeng Spring 2018

As an example, assume our critical section looks like this, the canonical update of a shared variable:

CS 333 Introduction to Operating Systems. Class 3 Threads & Concurrency. Jonathan Walpole Computer Science Portland State University

Concurrency: Signaling and Condition Variables

CS 333 Introduction to Operating Systems. Class 3 Threads & Concurrency. Jonathan Walpole Computer Science Portland State University

ANSI/IEEE POSIX Standard Thread management

Synchronization and Semaphores. Copyright : University of Illinois CS 241 Staff 1

CS 471 Operating Systems. Yue Cheng. George Mason University Fall 2017

POSIX Threads. HUJI Spring 2011

30. Condition Variables

CS 537: Introduction to Operating Systems Fall 2015: Midterm Exam #2 SOLUTIONS

CS 3305 Intro to Threads. Lecture 6

Synchronization I q Race condition, critical regions q Locks and concurrent data structures q Next time: Condition variables, semaphores and monitors

Threads. Today. Next time. Why threads? Thread model & implementation. CPU Scheduling

As an example, assume our critical section looks like this, the canonical update of a shared variable:

Threads. To do. Why threads? Thread model & implementation. q q q. q Next time: Synchronization

Pthreads. Jin-Soo Kim Computer Systems Laboratory Sungkyunkwan University

Interlude: Thread API

Process Synchronization (Part I)

Condition Variables. parent: begin child parent: end

Synchronization and Semaphores. Copyright : University of Illinois CS 241 Staff 1

Concurrent Programming Lecture 10

Threads. Threads (continued)

CS533 Concepts of Operating Systems. Jonathan Walpole

Threads. Jo, Heeseung

Introduction to PThreads and Basic Synchronization

Thread. Disclaimer: some slides are adopted from the book authors slides with permission 1

LSN 13 Linux Concurrency Mechanisms

pthreads CS449 Fall 2017

Threads. studykorner.org

Concurrent Server Design Multiple- vs. Single-Thread

Chapter 4 Concurrent Programming

Lecture 19: Shared Memory & Synchronization

Figure 30.1: A Parent Waiting For Its Child What we would like to see here is the following output:

CS-345 Operating Systems. Tutorial 2: Grocer-Client Threads, Shared Memory, Synchronization

CS 111. Operating Systems Peter Reiher

Preview. What are Pthreads? The Thread ID. The Thread ID. The thread Creation. The thread Creation 10/25/2017

Concurrency and Synchronization. ECE 650 Systems Programming & Engineering Duke University, Spring 2018

Thread and Synchronization

Why We Wait. IPC, Threads, Races, Critical Sections. Correct Ordering. Approaches to Waiting. Condition Variables 4/23/2018

High Performance Computing Course Notes Shared Memory Parallel Programming

Synchronization Primitives

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

Threads. What is a thread? Motivation. Single and Multithreaded Processes. Benefits

real time operating systems course

Concurrency: Mutual Exclusion (Locks)

POSIX threads CS 241. February 17, Copyright University of Illinois CS 241 Staff

[537] Semaphores. Tyler Harter

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

POSIX Threads. Paolo Burgio

Threads. Jo, Heeseung

HPCSE - I. «Introduction to multithreading» Panos Hadjidoukas

CS510 Operating System Foundations. Jonathan Walpole

Threads. Jinkyu Jeong Computer Systems Laboratory Sungkyunkwan University

Operating Systems. Pablo Prieto Torralbo. 4. Concurrency DEPARTMENT OF COMPUTER ENGINEERING

Posix Threads (Pthreads)

Process Synchronization

Pre-lab #2 tutorial. ECE 254 Operating Systems and Systems Programming. May 24, 2012

EPL372 Lab Exercise 2: Threads and pthreads. Εργαστήριο 2. Πέτρος Παναγή

CS 537: Introduction to Operating Systems (Summer 2017) University of Wisconsin-Madison Department of Computer Sciences.

Pthreads (2) Dong-kun Shin Embedded Software Laboratory Sungkyunkwan University Embedded Software Lab.

Multi-threaded Programming

CS 111. Operating Systems Peter Reiher

Real Time Operating Systems and Middleware

THREADS. Jo, Heeseung

OS Structure. User mode/ kernel mode. System call. Other concepts to know. Memory protection, privileged instructions

Multithreaded Programming

Multithreading Programming II

Why We Wait. Mutual Exclusion, Async Completions. Correct Ordering. Approaches to Waiting. Condition Variables 4/15/2017

CS333 Intro to Operating Systems. Jonathan Walpole

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

User Space Multithreading. Computer Science, University of Warwick

Data Races and Deadlocks! (or The Dangers of Threading) CS449 Fall 2017

Operating Systems CMPSCI 377 Spring Mark Corner University of Massachusetts Amherst

CS 537 Lecture 11 Locks

Threads need to synchronize their activities to effectively interact. This includes:

COSC 6374 Parallel Computation. Shared memory programming with POSIX Threads. Edgar Gabriel. Fall References

CS510 Operating System Foundations. Jonathan Walpole

Transcription:

Operating System Labs Yuanbin Wu CS@ECNU

Operating System Labs Project 4 Due: 10 Dec. Oral tests of Project 3 Date: Nov. 27 How 5min presentation 2min Q&A

Operating System Labs Oral tests of Lab 3 Who Principle: you should take at least one oral test We assume that you know all design/implementation details about your project

Operating System Labs Oral tests of Lab 3 Topics What have you done? How did you accomplish them? Project background data structures, algorithms, Your favorite parts. Features that you ve tried, but failed What did you learn from the project? Possible future improvements Highlight your new features

Operating System Labs Oral tests of Lab 3 Suggestions for your slides The clew model and onion model Minimize words, maximize pictures Simple and clear Large font Suggestions for your talk If you are an audience of your own talk... Design your rhythm, pauses, actions Practice Suggestions from Jonathan Shewchuk http://www.cs.berkeley.edu/~jrs/speaking.html

Operating System Labs I am trained to only sleep during national holidays

Operating System Labs Overview of concurrency Thread Two scenarios of concurrency control Locks Condition Variables Project 4

Thread Process and Thread In Linux, threads are processes with shared address space clone() system call with CLONE_THREAD Program Counter (PC) Process: one PC Threads: multiple PCs in a single thread Context switch Process: PCB Thread: TCBs in PCB

Thread Process and Thread Stack Process: one stack Thread: multiple stacks (thread local storage)

Thread Why threads Accelerate performance Light Weight Multiple processors, multi-core Faster creating and managing than processes Efcient communication Shared address space

Example 1: multithread #include <stdio.h> #include <assert.h> #include <pthread.h> void *mythread(void *arg) { printf("%s\n", (char *) arg); return NULL; Int main(int argc, char *argv[]) { pthread_t p1, p2; int rc; printf("main: begin\n"); rc = pthread_create(&p1, NULL, mythread, "A"); assert(rc == 0); rc = pthread_create(&p2, NULL, mythread, "B"); assert(rc == 0); // join waits for the threads to fnish rc = pthread_join(p1, NULL); assert(rc == 0); rc = pthread_join(p2, NULL); assert(rc == 0); printf("main: end\n"); return 0;

Example 2: multithread with shared objects #include <stdio.h> #include <assert.h> #include <pthread.h> static volatile int counter = 0; void * mythread(void *arg) { printf("%s: begin\n", (char *) arg); for (int i = 0; i < 1e7; i++) counter = counter + 1; printf("%s: done\n", (char *) arg); return NULL; int main(int argc, char *argv[]) { pthread_t p1, p2; int rc; printf("main: begin (counter = %d)\n", counter); rc = pthread_create(&p1, NULL, mythread, "A"); assert(rc == 0); rc = pthread_create(&p2, NULL, mythread, "B"); assert(rc == 0); rc = pthread_join(p1, NULL); assert(rc == 0); rc = pthread_join(p2, NULL); assert(rc == 0); printf("main: done with both (counter = %d)\n", counter); return 0;

Thread Terms Race condition Critical section Mutual exclusion

Thread Concurrency Control Where Processes with shared objects Threads How Atomic operations Helps from hardwares: Synchronized primitives

Thread POSIX Thread API pthread Thread create, control, destroy... Synchronized primitives Locks Condition variables Semaphore...

Thread pthread_create() Create a thread #include <pthread.h> Int pthread_create( pthread_t * thread, // structure of thread const pthread_attr_t * attr, // thread attributes void* (*start_routine)(void*), // the job void *arg // arguments of the job );

Thread pthread_join() Wait thread complete #include <pthread.h> int pthread_join( pthread_t thread, void **value_ptr ); // structure of thread // return value

Thread Locks (mutex) #include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int rc; rc = pthread_mutex_lock(&lock); assert(rc == 0); x = x + 1; // or whatever your critical section is pthread_mutex_unlock(&lock);

Thread Conditional variables #include <pthread.h> int pthread_cond_wait( pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_signal(pthread_cond_t *cond);

Thread Summary Processes with shared address space Concurrency control Critical section, race condition POSIX thread library pthread.h

Two Scenarios in Concurrency Control Protect critical sections Lock lock(), unlock() Syncronize diferent threads Conditional variable signal(), wait()

Two Scenarios in Concurrency Control Protect critical sections void func() { Thread 1 lock() /* start */ /* start */ /* end */ unlock() /* end */ void func() { Thread 2

Two Scenarios in Concurrency Control Syncronize threads void func1() {... /* wait some condition becomes true*/ Thread 1 void func2() {... /* set the condition */ Thread 2 Also called 1. Message passing 2. Communication 3. Syncronization

Two Scenarios in Concurrency Control Syncronize threads void func1() {... /* wait some condition becomes true*/ wait(condition_variable) void func2() {... /* set the condition */ signal(condition_variable)

Lock Lock: a variable lock_t mutex; // some globally-allocated lock mutex... lock(&mutex); balance = balance + 1; // critical section unlock(&mutex); Around critical sections Two states: free(unlocked) or held(locked)

Lock Two APIs lock(): try to acquire the lock If the lock is free (no other thread hold the lock) Get the lock and enter the critical section Won't return while the lock is not free unlock(): The state of the lock is changed to free One waiting thread (stuck by lock()) gets the lock

Lock Revoke some control from OS Threads are scheduled by OS Lock provide a way for programmer to break the scheduling

Lock How to implement a lock? Criteria Correctness Fairness Performance

Lock Lock implementation: disable interrupts void lock(){ disable_interrupts(); void unlock(){ enable_interrupts(); Problems Require privileged operation Only for single processor

Lock Lock implementation: using fag typedef struct lock_t { int fag; lock_t; void init(lock_t *mutex) { mutex->fag = 0; // 0 -> lock is free, 1 -> held void lock(lock_t *mutex) { while (mutex->fag == 1) // TEST the fag ; // spin-wait (do nothing) mutex->fag = 1; // now SET it! void unlock(lock_t *mutex) { mutex->fag = 0;

Lock Lock implementation: using fag Correctness Efciency Waste CPU time

Lock Lock implementation: atomic test-and-set int TestAndSet(int *old_ptr, int new) { int old = *old_ptr; // fetch old value at old_ptr *old_ptr = new; // store new into old_ptr return old; // return the old value Atomically! Hardware Instructions In x86 xchg

Lock Lock implementation: atomic test-and-set Spin lock Requires a preemptive scheduler typedef struct lock_t { int fag; lock_t; void init(lock_t *mutex) { mutex->fag = 0; // 0 -> lock is free, 1 -> held void lock(lock_t *mutex) { while (TestAndSet(&lock->fag, 1)== 1) ; // spin-wait (do nothing) void unlock(lock_t *mutex) { mutex->fag = 0;

Lock Lock implementation: atomic test-and-set Spin lock Correctness Fairness Yes No guarantees Performance Spin using CPU cycles Single processor: painful Multiple processors: reasonable

Lock Other hardware premitives test-and-set Compare-and-swap Load-linked and store-conditional Fetch-and-add Fairness: assign a ticket for each waiting thread

Lock typedef struct lock_t { int fag; lock_t; typedef struct lock_t { int fag; lock_t; void init(lock_t *mutex) { mutex->fag = 0; void init(lock_t *mutex) { mutex->fag = 0; void lock(lock_t *mutex) { while (mutex->fag == 1) ; mutex->fag = 1; void lock(lock_t *mutex) { while (TestAndSet(&lock->fag, 1)== 1) ; void unlock(lock_t *mutex) { mutex->fag = 0; Buggy fag void unlock(lock_t *mutex) { mutex->fag = 0; Spin lock

Lock Problems of spin locks Waste cpu time No gurantee on fairness (in general) N threads, only 1 hold the lock, other thread will spin starvation How to improve?

Lock Sleep instead of spin Assume an OS primitive yield() Move the caller from running to ready typedef struct lock_t { int fag; lock_t; void init(lock_t *mutex) { mutex->fag = 0; Problem: 1. cost of context switch is substantia 2. fairness: still not handled void lock(lock_t *mutex) { while (TestAndSet(&lock->fag, 1)== 1) yield(); void unlock(lock_t *mutex) { mutex->fag = 0;

Lock typedef struct lock_t { int fag; lock_t; typedef struct lock_t { int fag; lock_t; void init(lock_t *mutex) { mutex->fag = 0; void init(lock_t *mutex) { mutex->fag = 0; void lock(lock_t *mutex) { while (TestAndSet(&lock->fag, 1)== 1) ; void lock(lock_t *mutex) { while (TestAndSet(&lock->fag, 1)== 1) yield(); void unlock(lock_t *mutex) { mutex->fag = 0; void unlock(lock_t *mutex) { mutex->fag = 0; Spin lock Sleep instead of spin Question: How to improve fairness?

Lock Improve fairness Queue

Lock Improve fairness typedef struct lock_t { int fag; lock_t; queue_t q; enqueue/dequeue should be thread-safe. void init(lock_t *mutex) { mutex->fag = 0; init_queue(q); How to? 1. play queue operations in kernel 2. spin lock for the queue operation. void lock(lock_t *mutex) { while (TestAndSet(&lock->fag, 1)== 1) { enqueue(q); // put the thread in q yield(); void unlock(lock_t *mutex) { mutex->fag = 0; dequeue(q); // wakeup a thread in q

Lock Improve fairness Assume two OS primitives (from Solaris) park(): put the calling thread to sleep unpark(tid): wake a particular thread A queue: prevent starvation

typedef struct lock_t { int fag; int guard; queue_t *q; lock_t; void lock_init(lock_t *m) { m->fag = 0; m->guard = 0; queue_init(m->q); Questions: 1. does it avoid spin? does guard necessary? 2. can we change order? 3. why no fagg0 in unlock? 4. wakeup/waiting race void lock(lock_t *m) { while (TestAndSet(&m->guard, 1) == 1) ; //acquire guard lock by spinning if (m->fag == 0) { m->fag = 1; // lock is acquired m->guard = 0; else { queue_add(m->q, gettid()); m->guard = 0; park(); setpark(); park(); m->guard = 0; park(); void unlock(lock_t *m) { while (TestAndSet(&m->guard, 1) == 1) ; //acquire guard lock by spinning if (queue_empty(m->q)) m->fag = 0; // no one wants it else // hold lock m->fag = 0; (for next thread!) unpark(queue_remove(m->q)); m->guard = 0;

Lock sleeping instead of spin + spin on queue Two OS(Soloari) primitives park(), setpark() unpark() Avoid spinning by guard Avoid starvation by a queue

Lock Queue in kernel (the linux way) OS primitive: futex A memory location A in-kernel queue (per-futex)

Lock Futex: wait and wake futex_wait(address, expected) If (*address gg expected) put caller in the queue, let it sleep else Return immediately futex_wake(address) Wake one thread waiting for the futex Let's see some real code (lowlevellock.h of glibc)

void mutex_lock (int *mutex) { int v; // Bit 31 was clear, we got the mutex (this is the fastpath) if (atomic_bit_test_set (mutex, 31) == 0) return; atomic_increment (mutex); while (1) { if (atomic_bit_test_set (mutex, 31) == 0) { atomic_decrement (mutex); return; /* We have to wait now. First make sure the futex value we are monitoring is truly negative (i.e. locked). */ v = *mutex; if (v >= 0) continue; futex_wait (mutex, v); void mutex_unlock (int *mutex) { /* Adding 0x80000000 to the counter results in 0 if and only if there are not other interested threads */ if (atomic_add_zero (mutex, 0x80000000)) return; /* There are other threads waiting for this mutex, wake one of them up. */ futex_wake (mutex);

Lock Lock implementation: two-phase locks park/futex are system call! Spinning could be useful when the lock is about to release Two-phase locks In the frst phase: spins for a while In the second phase: sleep and wait Hybrid approach Above Linux lock is a two-phase lock Spin only once Can you implement a two-phase lock in your project?

Lock Lock implementation: summary Disable interrupts Flag Spin locks Test-and-set Compare-and-swap Fetch-and-add Sleep instead of spinning park(), setpark(), unpark() Futex Two-phase locks

Condition Variables Lock Protect critical sections Condition Variables A thread wait some condition becomes true Another thread signal changes of the condition wait(), signal()

Condition Variables void *child(void *arg) { printf("child\n"); // XXX how to indicate we are done? return NULL; int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t c; Pthread_create(&c, NULL, child, NULL); // XXX how to wait for child? printf("parent: end\n"); return 0;

Condition Variables volatile int done = 0; void *child(void *arg) { printf("child\n"); done = 1; return NULL; 1. Waste CPU cycles 2. May be incorrect int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t c; Pthread_create(&c, NULL, child, NULL); while (done == 0) ; printf("parent: end\n"); return 0;

Condition Variables Defnition A CV is an explicit queue Threads put themselves on the queue when some condition is not satisfed, and sleep Some other threads change the condition, and wake one/more threads in the queue Two API associated with a CV wait() signal()

Condition Variables POSIX calls (pthread.h) #include <pthread.h> // declear pthread_cond_t c; // wait() int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m); // signal() int pthread_cond_signal(pthread_cond_t *c);

int done = 0; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t c = PTHREAD_COND_INITIALIZER; void *child(void *arg) { printf("child\n"); thr_exit(); return NULL; int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t p; Pthread_create(&p, NULL, child, NULL); thr_join(); printf("parent: end\n"); return 0;

int done = 0; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t c = PTHREAD_COND_INITIALIZER; void thr_exit() { Pthread_mutex_lock(&m); done = 1; Pthread_cond_signal(&c); Pthread_mutex_unlock(&m); pthread_cond_signal(&c) Wakeup a waiting thread on c void *child(void *arg) { printf("child\n"); thr_exit(); return NULL; void thr_join() { Pthread_mutex_lock(&m); while (done == 0) Pthread_cond_wait(&c, &m); Pthread_mutex_unlock(&m); int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t p; Pthread_create(&p, NULL, child, NULL); thr_join(); printf("parent: end\n"); return 0; pthread_cond_wait(&c, &m) 1. assume the lock is held 2. put caller at the queue release the lock let caller sleep 3. when wakeup: - re-acquire the lock, - pop from the queue Consider two possibilities: 1. parent frst 2. child frst

int done = 0; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t c = PTHREAD_COND_INITIALIZER; void thr_exit() { Pthread_mutex_lock(&m); done = 1; Pthread_cond_signal(&c); Pthread_mutex_unlock(&m); Alternative 1: No done void thr_exit() { Pthread_mutex_lock(&m); Pthread_cond_signal(&c); Pthread_mutex_unlock(&m); void *child(void *arg) { printf("child\n"); thr_exit(); return NULL; void thr_join() { Pthread_mutex_lock(&m); while (done == 0) Pthread_cond_wait(&c, &m); Pthread_mutex_unlock(&m); int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t p; Pthread_create(&p, NULL, child, NULL); thr_join(); printf("parent: end\n"); return 0; void thr_join() { Pthread_mutex_lock(&m); Pthread_cond_wait(&c, &m); Pthread_mutex_unlock(&m);

int done = 0; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t c = PTHREAD_COND_INITIALIZER; void thr_exit() { Pthread_mutex_lock(&m); Pthread_cond_signal(&c); Pthread_mutex_unlock(&m); void *child(void *arg) { printf("child\n"); thr_exit(); return NULL; void thr_join() { Pthread_mutex_lock(&m); Pthread_cond_wait(&c, &m); Pthread_mutex_unlock(&m); int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t p; Pthread_create(&p, NULL, child, NULL); thr_join(); printf("parent: end\n"); return 0; Alternative 1: No done

int done = 0; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t c = PTHREAD_COND_INITIALIZER; void thr_exit() { Pthread_mutex_lock(&m); done = 1; Pthread_cond_signal(&c); Pthread_mutex_unlock(&m); Alternative 2: No lock void thr_exit() { done = 1; Pthread_cond_signal(&c); void *child(void *arg) { printf("child\n"); thr_exit(); return NULL; void thr_join() { Pthread_mutex_lock(&m); while (done == 0) Pthread_cond_wait(&c, &m); Pthread_mutex_unlock(&m); int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t p; Pthread_create(&p, NULL, child, NULL); thr_join(); printf("parent: end\n"); return 0; void thr_join() { if (done == 0) Pthread_cond_wait(&c);

int done = 0; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t c = PTHREAD_COND_INITIALIZER; void thr_exit() { done = 1; Pthread_cond_signal(&c); void *child(void *arg) { printf("child\n"); thr_exit(); return NULL; void thr_join() { if (done == 0) Pthread_cond_wait(&c); int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t p; Pthread_create(&p, NULL, child, NULL); thr_join(); printf("parent: end\n"); return 0; Alternative 2: No lock

Condition Variables Summary A queue Wait() and signal() Hold the lock!

Project 4 The Dark Forest of Threads