Software architecture and larger system design issues ecture 3: Monitor synchonization Topics: Programming language specifics (ACE) More on monitor synchronization More problems with concurrent software
Programming anguage Specifics C++: ibraries POSIX Threads (Pthreads) C library (see pthreads.h) ACE Portability layer on top of PThreads Adds some C++ niceties Boost Makes use of more advanced C++ features Java: First-class language features Monitor oriented synchronized keyword Thread class and Runnable interface provided
ACE Classes A ACE_Thread_Mutex: Mutex class acquire and release methods ACE_Condition_Thread_Mutex: Condition class constructor takes an ACE_Thread_Mutex wait, signal, and broadcast methods ACE_Thread_Manager singleton (has instance method) spawn method, which takes function pointer: void*(thread_root*)(void*) void pointer to arguments to pass to function spawn_n: like spawn but takes an additional int wait: block until all spawned threads are terminated
Recall: Multithreaded Server Design istener 1 1..* <<monitor>> 1 Buffer 1 put(c:connection) ():Connection Handler
Handout Figure 1: Buffer Class Definition 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Buffer public: Buffer(); Connection* (); void put(connection* conn); private: // Circular-array data structure Connection* carray[bufsiz]; int begin; int size; ACE_Thread_Mutex lock; // The monitor lock ACE_Condition_Thread_Mutex nonempty; ACE_Condition_Thread_Mutex nonfull; ;
Handout Figure 2: Buffer Method Definitions 21 22 23 Buffer::Buffer() : begin(0), size(0), nonempty(lock), nonfull(lock) 42 43 44 45 46 47 48 49 50 51 52 53 54 void Buffer::put(Connection* conn) carray[(begin + size) % BUFSIZ] = conn; ++size;
Handout Figure 2: Buffer Method Definitions 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 Connection* Buffer::() result = carray[begin]; ++begin; begin %= BUFSIZ; --size;
Handout Figure 3: Thread Root Functions 55 56 // Global shared connection fer Buffer ; 59 60 61 62 63 64 65 66 void* listener(void*) for (;;).put(accept_connection()); return 0; 67 68 69 70 71 72 73 74 void* handler(void*) for (;;) service_connection(.()); return 0;
Handout Figure 4: Main Function 76 77 78 79 80 81 82 83 int main(int, char*[]) ACE_Thread_Manager::instance()->spawn_n(1, listener, 0); ACE_Thread_Manager::instance()->spawn_n(4, handler, 0); ACE_Thread_Manager::instance()->wait(); return 0;
c nonempty = nonfull = data = ( c ) lock = (, )... Buffer::() in the lock fer, = (, and two ) handlers ( and ) concurrently invoke () on the fer lock = (, ) nonempty = Imagine a scenario where there is one connection (c)
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, ) is scheduled first...... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, ) Then, is preempted by... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
c nonempty = nonfull = data = ( c ) lock = (, )... Buffer::() enters lock the = (, blocking ) state, and is scheduled lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, ) Then, is preempted by... Buffer::() c lock = (, ) nonempty =
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, )
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, )
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, )
nonempty = nonfull = data = ( c ) lock = (, ) lock = (, )... Buffer::() c lock = (, ) nonempty =
put(d) nonempty = nonfull = lock = (, ) data = ( d )... Buffer::() put() nonempty the = fer while is waiting lock = (, ) d Now, imagine a listener thread invokes
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d )... Buffer::() the lock nonempty before = it can return from wait(). In the lock = (, ) d is no longer blocking, but it still must acquire meantime, continues running
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) Then, is preempted by nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, )
put(d) nonempty = nonfull = lock = (, ) data = ( d ) nonempty =... Buffer::() lock = (, ) d
In-Class Exercise: Draw a Sequence Diagram Assume: a listener thread,, and two handler threads, and, and the fer is empty, and all the threads are at the beginning of their respective control loops. Scenario: (1) calls and calls wait on the nonempty condition (does not return from wait yet). (2) calls and calls wait on the nonempty condition (does not return from wait yet). (3) calls put, adds a connection c to the fer, calls signal on the nonempty condition, and returns from put. (4) returns from wait, removes a c from the fer, and returns from.
Exercise: Solution Pt. 1 nonempty = nonfull = () lock = (, ) nonempty = () lock = (, ) nonempty =,
Exercise: Solution Pt. 2 put(c) lock = (, ) data = ( c ) nonempty = lock = (, ) c
More Problems with Concurrent Programs Previously introduced problems: Nondeterministic scheduling makes comprehension difficult Race conditions (data races) Need to structure design differently for concurrency New problems: Deadlock: All threads transition to the blocked state Components do not compose cleanly (hidden dependences)
Variant Server Design: Array of Buffers istener 1 1 <<monitor>> Buffer_Manager <<monitor>> Buffer put(c:connection) ():Connection 0..1 put_in_(c:connection, i:integer) _from_(i:integer):connection 1..* Handler 1 index: Integer 1 See the problem with this design?
mgr data = (0, 1) 0 nonempty = nonfull = _from_(0) lock = (, ) lock = (, ) nonempty = Scenario: Deadlock DEADOCK!
Still More Problems ivelock: ike deadlock, only with busy waiting Starvation: A thread cannot access to a resource Unfairness: Some threads have greater access to resources than others Priority inversion: Threads with higher scheduling priority make slower progress than lower priority threads Poor performance: Multithreaded program performs worse than sequential equivalent