Title: Concurrency: Classic Problems and Highlevel abstractions
1Concurrency Classic Problems and High-level
abstractions
Fred Kuhns (fredk_at_arl.wustl.edu,
http//www.arl.wustl.edu/fredk) Department of
Computer Science and Engineering Washington
University in St. Louis
2Classical Problems of Synchronization
- Bounded-Buffer Problem
- Readers and Writers Problem
- Dining-Philosophers Problem
3Bounded-Buffer Problem
- Shared datasemaphore full, empty,
mutexInitiallyfull 0, empty n, mutex 1
4Bounded-Buffer Producer
- do
- produce an item in nextp
- wait(empty) // Decrement free cnt
- wait(mutex) // Lock buffers
- add nextp to buffer
- signal(mutex) // release buffer lock
- signal(full) // Increment item count
- while (1)
5Bounded-Buffer Consumer
- do
- wait(full) // decrement item count
- wait(mutex) // lock buffers
- remove item from buffer nextc
- signal(mutex) // release lock
- signal(empty) // increment free count
- consume item in nextc
- while (1)
6Readers-Writers
- Reader tasks and writer tasks share a resource,
say a database - Many readers may access a database without fear
of data corruption (interference) - However, only one write may access the database
at a time (all other readers and writers must be
locked out of database. - Solutions
- simple solution gives priority to readers.
Readers enter CS regardless of whether a write is
waiting - writers may starve
- second solution requires once a write is ready,
it gets to perform its write as soon as possible - readers may starve
7Readers-Writers Problem
- The simple solution is presented
- Shared datasemaphore mutex, wrtInitiallymut
ex 1, wrt 1, readcount 0
8Readers-Writers Problem Writer
- wait(wrt)
-
- writing is performed
-
- signal(wrt)
9Readers-Writers Problem Reader
- wait(mutex)
- readcount
- if (readcount 1) // First reader
- wait(wrt) // then get lock
- signal(mutex)
- reading is performed
- wait(mutex)
- readcount--
- if (readcount 0) // Last reader
- signal(wrt) // then release lock
- signal(mutex)
10Dining-Philosophers Problem
- Shared data
- semaphore chopstick5
- Initially all values are 1
11Dining-Philosophers Problem
- Philosopher i
- do
- wait(chopsticki)
- wait(chopstick(i1) 5)
- eat
- signal(chopsticki)
- signal(chopstick(i1) 5)
- think
- while (1)
12Potential Problems
- Incorrect use of semaphores can lead to problems
- Critical sections using semaphores must keep to
a strict protocol - wait(S) critical section signal(S)
- Problems
- No mutual exclusion
- Reverse signal(S) critical section wait(S)
- Omit wait(S)
- Deadlock
- wait(S) critical section wait(S)
- Omit signal(S)
13Potential Solutions
- How do we protect ourselves from these kinds of
errors? - Develop language constructs that can be validated
automatically by the compiler or run-time
environment - Critical Regions
- Monitors
14Critical Regions
- High-level synchronization construct
- A shared variable v of type T, is declared as
- v shared T
- Variable v accessed only inside statement
- region v when B do Swhere B is a Boolean
expression. - While statement S is being executed, no other
process can access variable v.
15Critical Regions
- Regions referring to the same shared variable
exclude each other in time. - When a process tries to execute the region
statement, the Boolean expression B is evaluated.
If B is true, statement S is executed. If B is
false, the process is delayed until B becomes
true and no other process is in the region
associated with v.
16Critical Regions Bounded Buffer
- struct buffer
- int pooln
- int count, in, out
- Producer
- region buffer when (count lt n)
- poolin nextp
- in (in1) n
- count
-
Consumer region buffer when (count gt 0) nextc
poolout out (out1) n count--
17Monitors
- High-level synchronization construct that allows
the safe sharing of an abstract data type among
concurrent processes. - monitor monitor-name
- shared variable declarations
- procedure body P1 () . . .
- procedure body P2 () . . .
- procedure body Pn () . . .
- initialization code
-
18Schematic View of a Monitor
19Monitors Condition Variables
- To allow a process to wait within the monitor, a
condition variable must be declared, as - condition x, y
- Condition variable can only be used with the
operations wait and signal. - The operation
- x.wait()means that the process invoking this
operation is suspended until another process
invokes - x.signal()
- The x.signal operation resumes exactly one
suspended process. If no process is suspended,
then the signal operation has no effect.
20Monitors Condition Variables
- If a task A within a monitor signals a suspended
task B, there is an issue of who then gets to
continue executing within the monitor. - Task A waits for B to leave the monitor or waits
on another condition - Task B waits for A to leave the monitor or waits
for another condition - A case can be made for either approach
21Monitor With Condition Variables
22Dining Philosophers Example
- monitor dp
-
- enum thinking, hungry, eating state5
- condition self5
- void pickup(int i)
- void putdown(int i)
- void test(int i)
- void init()
- for (int i 0 i lt 5 i)
- statei thinking
-
-
23Dining Philosophers
- void pickup(int i)
- statei hungry
- test(i)
- if (statei ! eating)
- selfi.wait()
-
- void putdown(int i)
- statei thinking
- // test left and right
- test((i4) 5)
- test((i1) 5)
void test(int i) if ( (state(i 4) 5 !
eating) (statei hungry)
(state(i 1) 5 ! eating)) statei
eating selfi.signal() Solution dp.pi
ckup(i) ... eat ... dp.putdown(i)
24Monitor Implementation
- Since only one task may be active in a monitor
there must be a mutex protecting each method. - We also need to account for sending signals
within a monitor that is, guard against having
more than one active task within the monitor. - Our implementation uses semaphores for both
mutual exclusion (initialized to 1 for a mutex)
and counting the number of tasks waiting to be
resumed within the monitor (initialized to 0
counting semaphore)
25Example Implementation
- semaphore mutex // protect monitor
- semaphore next // waiting to be resumed
- int next_count 0
- Each external method M will be replaced by
- wait(mutex) // ensures mutual exclusion
- body of M
- if (next_count gt 0) // tasks waiting to be
resumed - signal(next) // resume them
- else
- signal(mutex) // else unlock monitor
- Mutual exclusion within a monitor is ensured.
26Example Implementation CV
- Implement so that the signaling task waits to
complete - For each condition variable cv, we have
- semaphore cv_sem // (initially 0)
- int cv_count 0
cv.signal if (cv_count gt 0) next-count
signal(cv_sem) wait(next) next-count--
cv.wait cv_count if (next_count gt
0) signal(next) else signal(mutex) wait(c
v_sem) cv_count--
27More on Monitor Implementations
- How can we control the task resumption order?
- Conditional-wait construct x.wait(c)
- c integer expression evaluated when wait
executed. - value of c (a priority number) stored with the
name of the process that is suspended. - when x.signal is executed, process with smallest
associated priority number is resumed next. - Verifying correctness Check two conditions
- User processes must always make their calls on
the monitor in a correct sequence. - Must ensure that an uncooperative process does
not ignore the mutual-exclusion gateway provided
by the monitor, and try to access the shared
resource directly, without using the access
protocols.
28OS Synchronization
- spin locks (simple mutex),
- blocking locks (mutex)
- adaptive mutex
- condition variables
29Spin Locks or Simple Mutexes
- The idea is to provide a basic, HW supported
primitive with low overhead. - Lock held for short periods of time
- If locked, then busy-wait on the resource
- Must not give up processor! In other words, can
not block.
30Spin-Lock implementation
void spin_lock (spinlock_t s) while
(test_and_set (s) ! 0) while (s ! 0)
void spin_unlock (spinlock_t s) s 0
31Blocking Locks/Mutex
- Allows threads to block
- Interface
- lock(), unlock () and trylock ()
- Consider traditional kernel locked flag
- Mutex allows for exclusive access to flag,
solving the race condition - flag can be protected by a spin lock.
32Condition variables
- Associated with a predicate which is protected by
a mutex (usually a spin lock). - Useful for event notification
- Can wakeup one or all sleeping threads!
33Condition Variables
- Up to 3 or more mutex are typically required
- one for the predicate
- one for the sleep queue (or CV list)
- one or more for the scheduler queue (swtch ())
- deadlock avoided by requiring a strict order
34Condition Variables
update predicate
wake up one thread
Thread sets event
35CV Implementation
Void do_signal (cv c) lock (cv-gtlistlock)
-- remove 1 thread -- unlock
(cv-gtlistlock) if thread, make runnable
return void do_broadcast (cv c) lock
(cv-gtlistlock) while (list is nonempty)
remove a thread make it runnable unlock
(cv-gtlistlock) return
wait() called with mutex s already locked. Void
wait (cv c, mutex_t s) lock
(cv-gtlistlock) -- add thread to queue --
unlock (cv-gtlistlock) unlock (s) swtch
() / return gt after wakup / lock (s)
return
36Solaris 2 Synchronization
- Implements a variety of locks to support
multitasking, multithreading (including real-time
threads), and multiprocessing. - Uses adaptive mutexes for efficiency when
protecting data from short code segments. - Uses condition variables and readers-writers
locks when longer sections of code need access to
data. - Uses turnstiles to order the list of threads
waiting to acquire either an adaptive mutex or
reader-writer lock.
37Windows 2000 Synchronization
- Uses interrupt masks to protect access to global
resources on uniprocessor systems. - Uses spinlocks on multiprocessor systems.
- Also provides dispatcher objects which are used
by user space threads and act as either mutexes,
semaphores or events. - An event acts much like a condition variable.
- signaled or unsignaled state