400 likes | 489 Views
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
E N D
Software Architecture and Larger System Design Issues Lecture 3:Synchronization Topics: Concurrent access to shared objects Thread synchronization Monitors CSE 335: Software Design
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
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
: Queue actor1 : … actor2 : … Example pull pull Suppose Queue initially contains <“hello”, “world”, “foo”>. What are the possible outcomes? CSE 335: Software Design
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
: Queue actor1 : … actor2 : … Question: Is this interaction possible? pull pull CSE 335: Software Design
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
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
Example interleaving actor1 : … :Queue actor2 : … pull empty pull empty back pop Question: What happens if queue contains A ? back pop CSE 335: Software Design
Example interleaving actor1 : … :Queue actor2 : … pull empty pull empty back pop Question: What happens if queue contains A, B ? back pop CSE 335: Software Design
What can go wrong here? Queue contains: A,B,C actor1 : … :Queue actor2 : … pull empty pull empty back back pop pop CSE 335: Software Design
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
Potential scenario actor1 : … :MonitorQueue actor2 : … pull empty pull back pop empty back pop CSE 335: Software Design
Another potential scenario actor1 : … :MonitorQueue actor2 : … pull pull empty back pop empty back pop CSE 335: Software Design
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
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
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
Exercise • Modify the design of the Queue class to protect its critical section(s) with locks. CSE 335: Software Design
Answer • class 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
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 T2invoke 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
The monitor-object pattern • Standard pattern for making instances of an arbitrary class behave like monitors. • Let C be the original class, and Mbe 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
Exercise • Use the monitor-object pattern to produce an alternative version of ThreadSafeQueue by extending the original Queue class. CSE 335: Software Design
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
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
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
Example interaction netSensor : … :Buffer reqHdlr : … push empty pull handle push push push empty pull CSE 335: Software Design
Another example interaction netSensor : … :Buffer reqHdlr : … push empty pull handle push push CSE 335: Software Design
Another example interaction netSensor : … :Buffer reqHdlr : … empty empty empty empty empty empty empty empty empty CSE 335: Software Design
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
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
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
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
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
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
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
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
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
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
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
Exercise • Design the logic for Buffer::empty so that the caller will block (go to sleep) when the buffer is empty CSE 335: Software Design