Lecture 9: Synchronizations

Fall 2018 Jason Tang

Slides based upon Concept slides, http://codex.cs.yale.edu/avi/os-book/OS9/slide-dir/index.html Copyright Silberschatz, Galvin, and Gagne, 2013 1 Topics

• Mutex Locks

• Semaphores

2 Mutex Locks

• test_and_set() and compare_and_swap() are hardware specific, and are abstracted by threading libraries

(mutex): Simplest general-purpose synchronizer

• Protects a by first acquire() a lock, and then release() the lock

• acquire() and release() must be atomic, usually implemented via hardware atomic instructions (x86’s CMPXCHG, ARM’s LDREX/STREX)

3 Mutex Pseudocode

mutex_t lock; /* some global variable */ /* producer code */ /* consumer code */ while (true) { while (true) { /* do work */ acquire(lock); acquire(lock); if (counter == 0) { counter++; release(lock); release(lock); continue; } } /* do work */ counter--; release(lock); }

4 Mutex Implementation

• Often involves a boolean variable indicating if lock is available or not

• release() is simply setting availability to true

: during acquire(), keep looping until lock is freed

• Also known as a spinlock

void release(mutex_t lock) { void acquire(mutex_t lock) { lock.available = true; while (!lock.available) } ; /* busy wait */ lock.available = false; }

5 Mutex Implementation

• Busy waiting on a single-processor system is inefficient (why?) void release(mutex_t lock) { void acquire(mutex_t lock) { reenable disable preemption } }

• Mutex can be implemented by enabling and disabling preemption

• Disabling preemption on a multi-processor system is inefficient (why?)

6 Pthread Mutex Creation

• In Pthread, a mutex is of type pthread_mutex_t

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);

• First parameter is address to store the mutex identifier

• Second parameter gives options for mutex, or to NULL for default settings

• Return value is always 0

• On Ubuntu, man pages for pthread_mutex_init() is in the glibc-doc package (sudo apt-get install glibc-doc)

7 Using Pthread Mutexes

int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_destroy(pthread_mutex_t *mutex);

• Call pthread_mutex_lock() to acquire a mutex

• Call pthread_mutex_unlock() to release the mutex

• Call pthread_mutex_destroy() to free memory associated with mutex

• For all three of these functions, the parameter is a pointer to the mutex created by pthread_mutex_init()

• Return value is 0 on success, negative on error

8 Pthread Mutex Example Code

/* initialization code */ pthread_mutex_t lock; pthread_mutex_init(&lock, NULL); /* producer thread */ /* consumer thread */ while (true) { while (true) { /* do work */ pthread_mutex_lock(&lock); pthread_mutex_lock(&lock); if (counter == 0) { counter++; pthread_mutex_unlock(&lock); pthread_mutex_unlock(&lock); continue; } } /* do work */ counter--; pthread_mutex_unlock(lock); } /* cleanup code */ pthread_mutex_destroy(&lock);

9 Semaphores

• Whereas mutexes are boolean, semaphores are used to count accesses

• When creating a semaphore, initialize it to some value (often 0 or 1)

• wait() (originally called P()) - if value is 0, block; otherwise decrement by 1 and continue

• Also known as down() or take()

() (originally called V()) - increment value by 1

• Also known as raise(), post(), up(), or give()

10 Semaphore Implementation void signal(semaphore_t sem) { void wait(semaphore_t sem) { sem++; while (sem <= 0) } ; /* busy wait */ sem--; }

• Must guarantee that no two processes are executing signal() / wait() on same semaphore simultaneously

• Critical section is now the semaphore implementation

• On many operating systems, semaphores are implemented partially in userspace, and thus faster than mutexes

11 Semaphore Subtypes

• Counting semaphore - value ranges from 0 to infinity

• Binary semaphore - value can only be 0 or 1

• If value is currently 0, signal() will increment it to 1

• If value is currently 1, signal() is a no-op

12 Semaphore Pseudocode

semaphore_t sem; /* some global variable */ sem = 0; /* initialize semaphore to 0 */ /* producer code */ /* consumer code */ while (true) { while (true) { /* do work */ wait(sem); signal(sem); /* do work */ } }

13 Pthread Semaphore Creation

• In Pthread, two types of semaphores (no binary semaphores):

• Unnamed semaphore: synchronizes threads and/or child processes int sem_init(sem_t *sem, int pshared, int value); • First parameter is address to store semaphore identifier

• If second parameter is 1, semaphore will be shared with child processes created by fork()

• Third parameter is initial value for semaphore (often 0 or 1)

• Named semaphore: synchronizes threads and/or unrelated processes

14 Using Pthread Semaphores

int sem_wait(sem_t *sem); int sem_post(sem_t *sem); int sem_destroy(sem_t *sem);

• Call sem_wait() to decrement semaphore, blocking if already 0

• Call sem_post() to increment semaphore

• Call sem_destroy() to free memory associated with an unnamed semaphore

• For all three of these functions, the parameter is a pointer to the semaphore created by sem_init()

• Return value is 0 on success, negative on error

15 Pthread Semaphore Example Code

/* initialization code */ sem_t sem; sem_init(&sem, 0, 0);

/* producer thread */ /* consumer thread */ while (true) { while (true) { /* do work */ sem_wait(&sem) sem_post(&sem); /* do work */ } }

/* cleanup code */ sem_destroy(&sem);

16

• Deadlock: two or more processes waiting indefinitely for each other

• Example 1: let S and Q be two semaphores initialized to 1 Thread A Thread B wait(S); wait(Q); wait(Q); wait(S); … … signal(S); signal(Q); signal(Q); signal(S); • Example 2: let S and Q be initialized to 0 Thread A Thread B wait(S); wait(Q); … … signal(Q); signal(S);

17 Starvation

• Starvation: indefinite blocking; some thread is always waiting Thread A while (true) { /* do work */ signal(sem); }

Thread B Thread C while (true) { while (true) { wait(sem); wait(sem); /* do work */ /* do different work */ } }

• If both threads B and C are waiting, and if OS always picks the thread B to get the semaphore, then C will never execute

18

• Scheduling problem when a lower-priority holds a lock needed by higher-priority process Thread A (high priority) Thread B (low priority) Thread C (medium priority) wait(sem); /* do work */ /* do work */ post(sem);

• Thread B unable to post semaphore because OS keeps scheduling thread C

• Can be solved via priority-inheritance protocol

• When a process accesses resources needed by a higher-priority process, it inherits the higher priority, until it has finished with those resources

19 Avoiding Deadlocks

• When acquiring multiple resources, release them in reverse order (like a stack)

• Use same acquisition order for all processes

Thread A Thread B wait(S); wait(S); wait(Q); wait(Q); … … signal(Q); signal(Q); signal(S); signal(S);

20 Dining Philosophers Problem

• Five philosophers sitting around a table, alternating thinking and eating

• No interaction with neighbors; occasionally try to pick 2 chopsticks (one at a time) to eat from bowl

• Need both to eat, then release both when done

21 Dining Philosophers Pseudocode

• Declare array of 5 semaphores chopstick[5], all initialized to 1

• Each philosopher i thread runs this code: while (true) { wait(chopstick[i]); wait(chopstick[(i + 1) % 5]); /* eat */ signal(chopstick[(i + 1) % 5]); signal(chopstick[i]); /* think */ }

• What is the problem with this ?

22 Resolving Dining Philosophers Algorithm

• Allow at most 4 philosophers at table

• Allow a philosopher to pick up chopsticks only if both are available

• Put “pick up chopsticks” in a critical section

• Use asymmetric solution: odd-numbered philosopher pickup up left and then right chopstick, while even-numbered philosopher picks right and then left

23