
Lecture 9: Thread Synchronizations Fall 2018 Jason Tang Slides based upon Operating System 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! • Deadlocks "2 Mutex Locks • test_and_set() and compare_and_swap() are hardware specific, and are abstracted by threading libraries! • mutual exclusion lock (mutex): Simplest general-purpose synchronizer! • Protects a critical section 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! • Busy waiting: 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 ine$cient (why?)! void release(mutex_t lock) { void acquire(mutex_t lock) { reenable preemption disable preemption } } • Mutex can be implemented by enabling and disabling preemption ! • Disabling preemption on a multi-processor system is ine$cient (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 set 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()! • signal() (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 • 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 Priority Inversion • Scheduling problem when a lower-priority process 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 algorithm? "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.
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages23 Page
-
File Size-