COMP 3511: Lecture 15
Date: 2024-10-24 15:15:04
Reviewed:
Topic / Chapter:
summary
βQuestions
Notes
Synchronization Tools
-
Semaphore
- semaphore : non-negative integer variable
- a generalized lock
- first defined by Dijkstra in late 1960s
- can behave similarly to mutex lock, but has more sophisticated usage
- min synchronization primitive used in original UNIX
- two standard operations for modifying:
wait()
andsignal()
- originally
P()
andV()
for proberen (test) and verhogen (increment) in Dutch
- originally
- must be execute atomically s.t. no more than one process: execute
wait()
andsignal()
on same semaphore at the same time- idea: serialization
- semaphore: only accessible by those two operations, except initialization
void wait(S) { while (S <= 0) ; // busy wait S--; } void signal (S) { S++; }
- semaphore : non-negative integer variable
-
Semaphore usage
- counting semaphore: integer value over unrestricted domain
- can be used: for controlling access to given set of resources
- of finite number of instances
- initialized to: no. of resources available
- binary semaphore: integer value, 0 or 1
- can be hav like mutex locks
- π¨βπ« but also can be used in different ways
- can also: solve various synchronization problems
- consider : sharing a common semaphore
synch
- initialized to 0- following code: ensures that process executes before execute
P_1: S_1; signal(synch); P_2: wait(synch); S_2;
- counting semaphore: integer value over unrestricted domain
-
Implementation with no busy waiting
- each semaphore: associated w/ waiting queue
- each entry in waiting queue: w/ two data ideas
- value (in integer)
- pointer to next record on queue
- two operations
- block: place process invoking operation to (appropriate) waiting queue
- wakeup: remove one of process in waiting queue
- place it on ready queue
- semaphore: may be negative
- w/ classical busy-waiting semaphore: impossible
- if semaphore value = negative:
- magnitude = no. of processes currently waiting on semaphore
-
Signal semaphore
- code
typedef struct{ int value; // if negative: processes being waiting struct process *list; } semaphore; wait(semaphore *S) { S->value--; if (S->value < 0) { add this process to S->list; block(); } } // signal: has chance of changing condition signal(semaphore *S) { S->value++; if (S->value <= 0) { remove a process P from S->list; wakeup(P); } }
- notice:
- increment & decrement: done before checking semaphore value
block()
: suspends the process & invokes itwakeup(P)
: resumes execution of a suspended process
- code
-
Deadlock and starvation
- semaphore initialized w/ (binary semaphore)
- forces atomic operation
- deadlock: two / more processes: waiting indefinitely
- for an event: that can be caused oy only one of waiting processes
- e.g. : with half-torn treasure map
- each: request the other part of map
- without giving up the piece they are holding
- won't happen!
- each: request the other part of map
- if : two semaphores initialized to :
wait(S);
wait(Q);
wait(Q);
wait(S);
signal(S);
signal(Q);
signal(Q);
signal(S);
- : waiting for to execute
signal(Q)
- and : waiting for to execute
signal(S)
- deadlock!
- and : waiting for to execute
- : waiting for to execute
- starvation: indefinite blocking
- process: may never being removed from semaphore queue
- then: suspended
- e.g. removing process from queue using LIFO
- w/ certain priorities
- process: may never being removed from semaphore queue
- semaphore initialized w/ (binary semaphore)
Synchronization Example
-
Bounded-buffer problem
- counting semaphore for solving a problem!
- buffers, each holding one item
- semaphore
mutex
initialized to1
- binary
- semaphore
full
initialized to0
- no. of full slots in buffer
- semaphore
empty
initialized to- signal(mutex);
- no. of empty slots in buffer
- structure of the producer process
do { // produce item wait(empty); wait(mutex); // guarantee mutual execution // add next produced to buffer signal(mutex); signal(full); // no. of items in buffer } while (true);
- structure of the consumer process
do { // remove item from budder to variable wait(full); wait(mutex); // guarantee mutual execution // consume next item from buffer signal(mutex); signal(empty); } while (true);
-
Readers-writers problem
- data set: shared among a no. of concurrent processes
- readers: only read the data, without performing any updates
- writers: can both read & write
- problem: allow multiple readers to read the data set at the same time
- however, at most one writer can access shared data at a time
- several variations on treatment of readers & writers
- w/ different priorities
- simplest solution: requiring no reader be kept waiting
- unless: writer as already gained access to shared data
- shared data update by writers: can be delayed
- as: people are reading (outdated) version
- gives: readers priority in accessing shared data
- shared data update by writers: can be delayed
- unless: writer as already gained access to shared data
- shared data
- data set
- semaphore
rw_mutex
initialized to 1 - semaphore
mutex
initialized to 1 - semaphore
read_count
initialized to 0
- for writer: guarantee mutual exclusion
do { wait(rw_mutex); // writing signal(rw_mutex); } while (true);
- for reader: first reader can take over
- prevent another writer from reading it!
do { wait(mutex); // exclusiveness of following snippet read_count++; if (read_count == 1) wait(rw_mutex); signal(mutex); // reading performed wait(mutex); read_count--; if (read_count == 0) signal(rw_mutex); signal(mutex); } while (true);
rw_mutex
: controls access to shared data for writers & first reader- last reader leaving: release the lock (for potential writer)
mutex
: controls the access of readers, to shared variableread_count
- writers: wait on
rw_mutex
- first reader gain access to critical section: also waiting on
rw_mutex
- all subsequent readers: wait on
mutex
(held by the first reader)
- first reader gain access to critical section: also waiting on
- data set: shared among a no. of concurrent processes
-
Reader-writer problem variations
- first variation: no reader kept waiting: unless writer gained access
- simple, yet might result in starvation of writers
- thus: potential & significant delay of the object's update
- simple, yet might result in starvation of writers
- second variation: one writer is reader: 'needs' perform update first
- when a writer: waiting for access (either another writer or readers occupying it)
- no new readers: can start reading
- either solution: may result in starvation
- problem: can be solved partially by kernel's reader-writer locks
- multiple processes: permitted to acquire lock in read mode
- yet: only one permitted to acquire lock in write mode
- i.e. specifying: the mode of the lock
- problem: can be solved partially by kernel's reader-writer locks
- first variation: no reader kept waiting: unless writer gained access
Synchronization by OSes
- by: Solaris, Windows XP, Linux, Pthreads
-
Solaris
- implements: variety of locks for support multi-tasking, multi-threading, and multi-processing
- uses: adaptive mutex for efficiency
- when protecting data from short code segments (less than a few hundred instructions)
- starts as: standard semaphore implemented as a spinlock in multiprocessor system
- lock held by a thread running on another CPU: spins to wait for lock
- if held by non-run-state thread: block & sleep waiting
- for signal of lock being released
- condition variables: to be discussed
- wait until condition satisfies
- uses: readers-writers locks when longer sections of code need access to data
- used to protect frequently-accessed data
- used in read-only manner
- relatively expensive to implement
- used to protect frequently-accessed data
-
Windows synchronization
- kernel: using interrupt masks to protect: access to global resources in uni-processor systems
- kernel: uses spinlocks for multi-processor systems
- for efficiency: threads are never preempted when holding a spinlock
- thread synchronization: outside the kernel (user mode)
- Windows: provide dispatcher objects
- threads: synchronize according to several different mechanism
- mutex locks, semaphores, events, timers
- events: similar to condition variables; notify waiting thread when condition occurs
- timers: used to notify one / more thread that specific amount of time has expired
- dispatcher objects: either signaled-state (object available) or non-signaled state (occupied & block / wait)
-
Linux synchronization
-
prior to kernel 2.6: disables interrupts to implement short critical sections
- 2.6 and later: fully preemptive kernel
-
provides:
- semaphores
- spinlocks (for multi-processor)
- atomic integer, and math operations involving it
- reader-writer locks
-
on single CPU: spinlocks replaced by enabling / disabling kernel preemption
-
atomic variables: like
atomic_t
- e.g. variable
atomic_t counter; int value;
atomic operation effect atomic_set(&counter, 5);
counter = 5
atomic_add(10, &counter);
counter = counter + 10
atomic_sub(4, &counter);
counter = counter - 4
atomic_inc(&counter);
counter = counter + 1
value = atomic_read(&counter);
value = 12
- e.g. variable
-
-
POSIX synchronization
- POSIX API: provides
- mutex locks
- semaphores
- condition variables
- widely used on UNIX, Linux, an MAcOS
- creating & initializing the lock
#include <pthread.h> pthread_mutex_t mutex; /* create and initialize the mutex lock */ pthread_mutex_init(&mutex, NULL);
- acquiring and releasing the lock
/* acquire the mutex lock */ pthread_mutex_lock(&mutex); /* critical section */ /* release the mutex lock */ pthread_mutex_unlock(&mutex);
- POSIX condition variables
- associated w/ POSIX mutex lock to provide: mutual exclusion
- π¨βπ« must be in combination
- modification of condition variable itself: not guaranteed to be exclusive
- mutex lock needed
pthread_mutex_t mutex; pthread_cond_t cond_var; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond_var, NULL);
- waiting for condition
a==b
to be true:pthread_mutex_lock(&mutex); while (a != b) pthread_cond_wait(&cond_var, &mutex); pthread_mutex_unlock(&mutex);
pthread_cond_wait
: in addition to putting the calling thread to sleep- release the lock when putting said caller to sleep
- no other signal: can acquire lock / signal it to wake up
- it also: release on
mutex
- thus: others can modify the condition..?
pthread_mutex_lock(&mutex); a = b; pthread_cond_signal(&cond_var); pthread_mutex_unlock(&mutex);
- when signalling: make sure to have the lock held
- ensures: there is no race condition
- before returning & being waked up:
pthread_cond_wait
re-acquires the lock- ensures: any time: waiting thread is running "between the lock acquired in the beginning"
- lock: release at the end
- π¨βπ« there are situation mutex can replace condition variable
- but there are also situations it can't
- POSIX API: provides