software architecture and larger system design issues lecture 3: synchronization
DESCRIPTION
Software Architecture and Larger System Design Issues Lecture 3: Synchronization. Topics: Concurrent access to shared objects Thread synchronization Monitors. Outline of course topics. Foundational OO concepts Synthetic concepts Software architecture and larger design issues - PowerPoint PPT PresentationTRANSCRIPT
CSE 335: Software Design K. Stirewalt
Software Architecture and Larger System Design Issues
Lecture 3: Synchronization
Topics:– Concurrent access to shared objects– Thread synchronization– Monitors
CSE 335: Software Design K. Stirewalt
Outline of course topics
Foundational OO concepts
Synthetic concepts
Software architecture and larger design issues– Example concern: Implementing systems with multiple loci of
control– Active objects– Techniques for implementing active objects:
• Have one actor (e.g., the GUIManager) periodically “cede” small quanta of control to other actors, which must be designed to perform their task in a series of small steps
• Allocate a system thread to “power” an actor
Software process issues
CSE 335: Software Design K. Stirewalt
Concurrent access to shared data
Problem: Multiple active objects might access the same passive object “at the same time”– Generally OK if the active objects are only reading data
from the passive object(s)• but even this can be dangerous if the “reader” methods use
iterators or some other mutable operation– But if one or more active objects is modifying the data
members of the shared object, then we get anomalies
Example: Two active objects trying to pull elements from the same queue
To prevent these data-access anomalies requires synchronizing the active objects.
CSE 335: Software Design K. Stirewalt
Example
pull
pull
: Queueactor1 : … actor2 : …
Suppose Queue initially contains <“hello”, “world”, “foo”>.What are the possible outcomes?
CSE 335: Software Design K. Stirewalt
Possible outcomes
Actor1 gets “hello”, actor2 gets “world”, queue contains <“foo”>
Actor1 gets “world”, actor2 gets “hello”, queue contains <“foo”>
Both actor1 and actor2 get “hello”, queue contains <“world”, “foo”>
Other possibilities, including corruption of internal state of queue...
CSE 335: Software Design K. Stirewalt
Question: Is this interaction possible?
pull
pull
: Queueactor1 : … actor2 : …
CSE 335: Software Design K. Stirewalt
The sad news…
While the use of multiple threads is very powerful, to avoid errors requires:– Reasoning that “breaks” modularity, i.e., thinking
about how methods are implemented and how methods they invoke are implemented
– Reasoning about a large number of possible thread interleavings
CSE 335: Software Design K. Stirewalt
Example
class Queue { public:
… bool pull( string& s ) { bool retval = q.empty(); if (!retval) {
s = q.back();q.pop();
} return retval; }
… protected:
queue<string> q;};
CSE 335: Software Design K. Stirewalt
Example interleaving
:Queue actor2 : …actor1 : …
pull
empty
back
pop
pull
empty
back
pop
Question: What happens if queue contains A ?
CSE 335: Software Design K. Stirewalt
Example interleaving
:Queue actor2 : …actor1 : …
pull
empty
back
pop
pull
empty
back
pop
Question: What happens if queue contains A, B ?
CSE 335: Software Design K. Stirewalt
What can go wrong here?Queue contains: A,B,C
:Queue actor2 : …actor1 : …
pull
empty
back
pop
pull
empty
back
pop
CSE 335: Software Design K. Stirewalt
To prevent unsafe interleavings
Promote shared object into a monitor – high-level synchronization construct– contains an implicit lock – only one thread can be
executing within the monitor at one time
Concurrent activations of monitor operations:– execute in some order without overlap– i.e., are serialized but the exact order of execution is
not defined• Note: There is an extension to monitors that allows control
over this ordering
CSE 335: Software Design K. Stirewalt
Potential scenario
:MonitorQueue actor2 : …actor1 : …
pull
empty
backpop
pull
empty
back
pop
CSE 335: Software Design K. Stirewalt
Another potential scenario
:MonitorQueue actor2 : …actor1 : …
pull
empty
backpop
pull
empty
back
pop
CSE 335: Software Design K. Stirewalt
Thread synchronization
Definitions:– Critical section: region of code in which at most one
thread should be allowed to execute concurrently– Mutex lock: OS facility used to synchronize threads
• One and only one thread can own a lock• Thread comes to own a lock by acquiring it• A thread will block if it attempts to acquire a lock owned by
another thread
Important: Whenever you write multi-threaded programs, you must identify and protect critical sections in your code!
CSE 335: Software Design K. Stirewalt
Multi-threaded programming
C++ provides no language features for thread programming– Contrast with Java, which does provide such features– In C++, threads and thread operations are provided by
standard libraries
In Unix, standard threads library is “pthreads”– Short for POSIX threads– Include files: /usr/include/pthread.h– Link library: libpthreads.a
A more object-oriented solution is the ACE library, built atop pthreads
CSE 335: Software Design K. Stirewalt
Primitives for using mutex locks
ACE_Thread_Mutex: type used to declare a lock
acquire: acquires a lock, blocking if lock owned by another thread
release: releases a lock, so that other threads may acquire it
CSE 335: Software Design K. Stirewalt
Exercise
Modify the design of the Queue class to protect its critical section(s) with locks.
CSE 335: Software Design K. Stirewalt
Answerclass ThreadSafeQueue { public: ThreadSafeQueue() {}
… bool pull( string& s ) { lock.acquire(); bool retval = q.empty(); if (!retval) {
s = q.back();q.pop();
} lock.release(); return retval; }
protected:ACE_Thread_Mutex lock;queue<string> q;
};
CSE 335: Software Design K. Stirewalt
Monitor synchronization
Defn: Monitor is an object whose methods may not be executed by multiple threads concurrently
Example:– Let o be a monitor that provides the operation:
void foo()
– Suppose threads T1 and T2 invoke o.foo() at nearly the same time
– One thread (e.g., T1) will be allowed to “enter the monitor”; the other (e.g., T2) must wait
Most OO languages use monitor synchronization
CSE 335: Software Design K. Stirewalt
The monitor-object pattern
Standard pattern for making instances of an arbitrary class behave like monitors.
Let C be the original class, and M be the (new) monitor class:– M should inherit publicly from C– M should contain a protected data member
(call it lock) of type ACE_Thread_Mutex– For each public method m of C, M should
override that method with one that acquires lock, invokes C::m and then releases lock
CSE 335: Software Design K. Stirewalt
Exercise
Use the monitor-object pattern to produce an alternative version of ThreadSafeQueue by extending the original Queue class.
CSE 335: Software Design K. Stirewalt
Answer
class MonitorQueue : public Queue { MonitorQueue() {} ~MonitorQueue() {}
bool pull( string& s ) {
lock.acquire(); bool retval = Queue::pull(s); lock.release(); return retval; }
protected: ACE_Thread_Mutex lock;};
CSE 335: Software Design K. Stirewalt
Design uses of synchronization
Monitors used to control access to shared data by preventing two threads from executing same method simultaneously.
Provides a very primitive form of coordination among active objects
In more complex interactions, an actor might wish to “wait” for another actor to perform some task and be “signaled” when once the other actor completes the task
We refer to this as condition synchronization
CSE 335: Software Design K. Stirewalt
Example
Suppose we are implementing a web server that accepts incoming network connections containing http requests and processes these requests in order
Requests take some time to perform, so to be fair, we would like to “queue them up” upon arrival and dispatch them in order
CSE 335: Software Design K. Stirewalt
Example interaction
:Buffer reqHdlr : …netSensor : …
push
empty
handle
push
pull
push
push
pull
empty
CSE 335: Software Design K. Stirewalt
Another example interaction
:Buffer reqHdlr : …netSensor : …
push
handlepull
empty
push
push
CSE 335: Software Design K. Stirewalt
Another example interaction
:Buffer reqHdlr : …netSensor : …
empty
empty
empty
empty
empty
empty
empty
empty
empty
CSE 335: Software Design K. Stirewalt
Issues
Obviously, buffer needs to be a monitor
We might also like to reduce the useless work performed by reqHdlr when the buffer is empty
Likewise might wish to start dropping requests when the buffer is full
Somehow, the state of the buffer needs to affect the execution of netSensor and reqHdlr
CSE 335: Software Design K. Stirewalt
Condition synchronization
Another form of synchronization that allows threads to “give up” a mutex lock and go to sleep until later notified by another thread
CSE 335: Software Design K. Stirewalt
Condition variables
Objects that attempt to reify conditions or states of other objects for the purpose of synchronization
Implementation:– ACE_Condition_Thread_Mutex used to declare a
condition variable (parameterized by a lock)– wait causes invoking thread to “go to sleep” and
release the lock until such time as some other thread invokes the signal operation on the condition variable
– signal wakes one of the waiting threads, and makes it enter into contention for the lock
CSE 335: Software Design K. Stirewalt
Declaration of class Buffer
class Buffer { public: Buffer(); void push( const string & ); bool pull( string& ); bool empty();
protected: queue<string> requestQ; ACE_Thread_Mutex lock; ACE_Condition_Thread_Mutex fullCond; ACE_Condition_Thread_Mutex empty;};
CSE 335: Software Design K. Stirewalt
Declaration of class Buffer
class Buffer { public: Buffer(); void push( const string & ); bool pull( string& ); bool empty();
protected: queue<string> requestQ; ACE_Thread_Mutex lock; ACE_Condition_Thread_Mutex fullCond; ACE_Condition_Thread_Mutex empty;};
Declares monitor lock
CSE 335: Software Design K. Stirewalt
Declaration of class Buffer
class Buffer { public: Buffer(); void push( const string & ); bool pull( string& ); bool empty();
protected: queue<string> requestQ; ACE_Thread_Mutex lock; ACE_Condition_Thread_Mutex fullCond; ACE_Condition_Thread_Mutex empty;}; Declares 2 condition
variables (full & empty)
CSE 335: Software Design K. Stirewalt
Example
void Buffer::push( const string & s ){ lock.acquire();
while(requestQ.full()) fullCond.wait();
requestQ.push_back(s);
if (requestQ.size() == 1) emptyCond.signal();
lock.release();}
CSE 335: Software Design K. Stirewalt
Example
void Buffer::push( const string & s ){ lock.acquire();
while(requestQ.full()) fullCond.wait();
requestQ.push_back(s);
if (requestQ.size() == 1) emptyCond.signal();
lock.release();}
Acquires monitor lock
Releases monitor lock
CSE 335: Software Design K. Stirewalt
Example
void Buffer::push( const string & s ){ lock.acquire();
while(requestQ.full()) fullCond.wait();
requestQ.push_back(s);
if (requestQ.size() == 1) emptyCond.signal();
lock.release();}
Waits signal from another thread
CSE 335: Software Design K. Stirewalt
Example
void Buffer::push( const string & s ){ lock.acquire();
while(requestQ.full()) fullCond.wait();
requestQ.push_back(s);
if (requestQ.size() == 1) emptyCond.signal();
lock.release();}
Signals any thread blocked on the empty condition
CSE 335: Software Design K. Stirewalt
Notes
Critically important to embed the wait of a condition variable in a loop that checks the logical negation of the condition– Reason: A significant amount of time could
pass between when waiting thread is signaled and it reacquires the lock
– Possible that the condition might no longer be true when the thread awakens
CSE 335: Software Design K. Stirewalt
Exercise
Design the logic for Buffer::empty so that the caller will block (go to sleep) when the buffer is empty