Title: COTS Challenges for Embedded Systems
1E81 CSE 532S Advanced Multi-Paradigm Software
Development
Synchronization Patterns
Christopher Gill, Todd Sproull, Eric
DeMello Department of Computer Science and
Engineering Washington University, St.
Louis cdgill_at_cse.wustl.edu
2An Illustrative Haiku
- Threads considered bad.
- So non-deterministic.
- What will happen nxte?
- - Justin Wilson, Magdalena Cassel, Adam
Drescher, Chris Gill
3Design for Multithreaded Programming
- Concurrency
- Logical (single processor) instruction
interleaving - Physical (multi-processor) parallel execution
- Safety
- Threads must not corrupt objects or resources
- More generally, bad inter-leavings must be
avoided - Atomic runs to completion without being
preempted - Granularity at which operations are atomic
matters - Liveness
- Progress must be made (deadlock is avoided)
- Goal full utilization (something is always
running)
4Multi-Threaded Design, Continued
- Benefits
- Performance
- Still make progress if one thread blocks (e.g.,
for I/O) - Preemption
- Higher priority threads preempt lower-priority
ones - Drawbacks
- Object state corruption due to race conditions
- Resource contention (overhead, latency costs)
- Need isolation of inter-dependent operations
- For concurrency, synchronization patterns do this
- At a cost of reducing concurrency somewhat
- And at a greater risk of deadlock
5Multi-Threaded Design, Continued
- Race conditions (threads racing for access)
- Two or more threads access an object/resource
- The interleaving of their statements matters
- Some inter-leavings have bad consequences
- Example (critical sections)
- Object has two variables x ? A,C, y ? B,D
- Allowed states of the object are AB or CD
- Assume each write is atomic, but writing both is
not - Thread t writes x A and is then preempted
- Thread u writes x C y D and blocks
- Thread t writes y B
- Object is left in an inconsistent state, CB
6Multi-Threaded Programming, Continued
- Deadlock
- One or more threads access an object/resource
- Access to the resource is serialized
- Chain of accesses leads to mutual blocking
- Single-threaded example (self-deadlock)
- A thread acquires then tries to reacquire same
lock - If lock is not recursive thread blocks itself
- Two thread example (deadly embrace)
- Thread t acquires lock j, thread u acquires lock
k - Thread t tries to acquire lock k, blocks
- Thread u tries to acquire lock j, blocks
7Synchronization Patterns
- Scoped Locking (via the C RAII Idiom)
- Ensures a lock is acquired/released in a scope
- Thread-Safe Interface
- Reduce internal locking overhead
- Avoid self-deadlock
- Strategized Locking
- Customize locks for safety, liveness,
optimization - These complement a number of concurrency patterns
that well cover as well over time
8Scoped Locking Pattern
- Intent
- Ensure lock is acquired when control enters a
scope and is released automatically when control
leaves, by any path - Context
- Code that should not execute concurrently, should
be protected (made atomic) by a lock - However it is hard to ensure that locks are
released in all paths through the code - C code can leave a scope due to a return,
break, continue, or goto statement, or a
propagating exception
9Scoped Locking, Continued
- Solution
- Define a guard whose constructor automatically
acquires a lock when control enters a scope - Destructor automatically releases the lock when
it leaves the scope - // from Williams pp. 38
- void add_to_list (int new_value)
- // RAII (a.k.a. guard idiom)
- stdlock_guardltstdmutexgt guard
(some_mutex) - // may throw an exception (e.g., bad_alloc)
- some_list.push_back(new_value)
10Thread-Safe Interface Pattern
- Intent
- Minimizes locking overhead
- Ensures intra-component method calls do not
self-deadlock - Context
- Intra-Component method calls
- public methods (accessible from outside a class)
- private implementations which change component
state - Recursive mutex higher overhead
- Non-recursive mutex risk of deadlock
11Thread-Safe Interface, Continued
- Solution
- Separate locking from implementation
- Encapsulate acquire/release within public
interface methods - at the border
- Encapsulate implementation in private methods
- Do not acquire/release
- Important restriction do not call up to public
interface methods
public void init () stdlock_guardltstdmut
exgt guard (my_mutex) // ... does
something inner_call () void foo ()
stdlock_guardltstdmutexgt guard
(my_mutex) // ... does something else
inner_call () void inner_call () // ...
does not take a lock
12Thread-Safe Interface, Continued
- Variant thread-safe façades and wrapper façades
- Synchronize an entire subsystem or API
- Calls (e.g., into OS kernel) may block until
completion - Benefits
- Helps prevent Intra-Component-Incurred-Self-Deadlo
ck - Use stdunique_lock and stdadopt_lock to
transfer lock ownership - Use stdlock function to grab gt1 mutexes at once
- See Williams Chapter 3.2 for more on these issues
- Helps avoid unnecessary acquire/release calls
- Allows addition of thread-safe wrappers to legacy
code
13Strategized Locking Pattern
- Intent
- Parameterizes synchronization mechanisms that
protect a components critical section from
concurrent access - Context
- Components can be re-used efficiently within a
variety of different concurrent applications - Different applications might need different
synchronization strategies - Mutex
- Readers/Writer locks
- Semaphores
- Solution
- Parameterize code with lock type to decouple them
- Can modify locks without changing application
logic - Can modify application w/o changing lock
implementations
14Strategized Locking, Continued
- Solution illustrated
- Parameterized synchronization protects critical
sections - Can plug in stdmutex or stdrecursive_mutex or
your own readers-writer lock or null lock, etc.
as needed - template ltclass LOCKgt
- class File_Cache
- public
- const void access (const string path)
- stdlock_guardltLOCKgt guard (lock_)
- //... implementation of the access method
-
- private
- LOCK lock_
15One More Design Issue Single Initialization
- Double Checked Locking Optimization etc. have
limitations - Generally speaking, hard to eliminate data races
without interface design - C11 introduces helpful stdonce_flag and
stdcall_once - Each thread calls stdcall_once
- Initialization guaranteed before the call returns
- // from Williams pp. 61
- void foo ()
- // call_once protects call to
init_resource - stdcall_once(resource_flag,init_resour
ce) - // thread-safe because resource is
initialized - resource_ptr-gtdo_something()
-