Title: Concurrencysynchronization using UML state models
1Concurrency/synchronization using UML state models
2Overview State models of monitor objects
- Monitor-process pattern
- Heuristic for generating correct
synchronization code from a state model
3State diagrams
- Useful for modeling space of behaviors of an
object or a system of interacting objects - Requires
- Identifying and naming the conceptual states
that an object might be in, and - Conceivable transitions among those states and
the events (and/or conditions) that trigger
(and/or guard) these transitions - Concurrent compositions of state diagrams can be
executed to expose anomalous behaviors
4Method for using models to design concurrent
software
- State model for each system object
- Passive objects modeled using monitor-process
pattern - Models then refined into code skeletons
- Active objects incorporate infrastructure needed
to host a thread - E.g., a distinguished run method whose activation
corresponds to the lifetime of a thread - Passive objects designed to behave as monitors
- Design pattern for passive-object model
guarantees monitor-object pattern can be applied
to develop code - Guarded transitions in state model engender
condition synchronization in code
5Simple monitor-process pattern
Monitor process
/ reply (op, result )
Idle
guard / accept-call ( op(parms) )
Operation body
6Simple monitor-process pattern
- Simple pattern
- Distinguished initial state Idle
- Composite state for each monitor operation
- Reachable only from Idle via an accept-call
action - Entry is named according to the name of the
operation - Possibly guarded with an enabling condition
- Must transition back to Idle via a reply action
upon exit - Names the entry to which it corresponds
- Includes return value (if any)
- No accept-call actions within the composite state
- Variations on this pattern will relax some of
these restrictions
7UML 2.0 Semantics Asynchronous events run to
completion
- Run-to-completion semantics
- State machine processes one event at a time and
finishes all consequences of that event before
processing another event - Events do not interact with one another during
processing - Event pool
- Where new events for an object are stored until
object is ready to process them - No event ordering assumed in the pool
8Observations
- Modeling method invocations as asynchronous
events which are placed in a pool - Requests for service on an object
- buffered up on arrival
- dispatched when the object is ready to handle
them - Natural interpretation for how an active object
can be invoked - Makes passive objects appear to execute with
monitor semantics
9Bounded Buffer Example
BoundedBuffer
Pulling
queue_.size ! 0 / accept-call ( pull )
do/ rv queue_.pull
/ reply ( pull, rv )
/ reply ( push )
queue_.size ! MAX / accept-call ( push(x) )
Pushing
do/ queue_.push(x)
10From simple monitor process to monitor object
(code)
- Class declares a private mutex variable lock
- Each operation state in the model becomes a
monitor method in the code - Method body bracketed by acquire/release of lock
- If any transition out of Idle is guarded
- Class must declare a condition variable,
associated with lock, to wait on when guard
condition is NOT satisfied - Method body includes a condition?wait loop
immediately following acquisition of lock - All other methods must notify this condition
variable if they might serve to satisfy this
guard condition - Notification could be signal or broadcast
11Example Code for Bufferpush
queue_.size() ! MAX / accept-call (push(x))
Idle
- void Bufferpush(int x)
-
- lock_.acquire()
- while (queue_.size() MAX)
- full_.wait()
-
- queue_.push(x)
- empty_.signal()
- lock_.release()
/ reply (push)
Pushing
do/ queue_.push(x)
12Example Code for Bufferpush
queue_.size() ! MAX / accept-call (push(x))
Idle
- void Bufferpush(int x)
-
- lock_.acquire()
- while (queue_.size() MAX)
- full_.wait()
-
- queue_.push(x)
- empty_.signal()
- lock_.release()
/ reply (push)
Pushing
do/ queue_.push(x)
13Example Code for Bufferpush
queue_.size() ! MAX / accept-call (push(x))
Idle
- void Bufferpush(int x)
-
- lock_.acquire()
- while (queue_.size() MAX)
- full_.wait()
-
- queue_.push(x)
- empty_.signal()
- lock_.release()
/ reply (push)
Pushing
do/ queue_.push(x)
14Example Code for Bufferpush
queue_.size() ! MAX / accept-call (push(x))
Idle
- void Bufferpush(int x)
-
- lock_.acquire()
- while (queue_.size() MAX)
- full_.wait()
-
- queue_.push(x)
- empty_.signal()
- lock_.release()
/ reply (push)
Pushing
do/ queue_.push(x)
15Example Code for Bufferpull
queue_.size() ! 0 / accept-call (pull)
Idle
- int Bufferpull()
-
- lock_.acquire()
- while (queue_.size() 0)
- empty_.wait()
-
- int rv queue_.pull()
- full_.signal()
- lock_.release()
- return rv
/ reply (pull, rv)
Pushing
do/ rv queue_.pull()
16More complex guard conditions
- Consider a banking application that allows
multiple clients to deposit and withdraw funds
from shared accounts - Goals
- Protect against data races, so that money is not
accidentally created or destroyed - Prevent overdrafts by making withdraw requests
block if account has insufficient funds - Question How should we model the behavior of an
account object?
17Monitor-process model of BankAccount
BankAccount
Depositing
/ accept-call ( deposit(amount) )
do/ balance_ amount
/ reply ( deposit )
/ reply ( withdraw )
Withdrawing
amount lt balance_ / accept-call (
withdraw(amount) )
do/ balance_ - amount
18Code for BankAccountwithdraw
amount lt balance_ / accept-call
(withdraw(amount))
Idle
- void
- BankAccountwithdraw(int amount)
-
- lock_.acquire()
- while (amount gt balance_)
- okToWithdraw_.wait()
-
- balance_ - amount
- lock_.release()
/ reply (withdraw)
Withdrawing
do/ balance_ - amount
19Code for BankAccountdeposit
/ accept-call (deposit(amount))
Idle
- void
- BankAccountdeposit(int amount)
-
- lock_.acquire()
- balance_ amount
- okToWithdraw_.broadcast()
- lock_.release()
/ reply (deposit)
Depositing
do/ balance_ amount
20Signal vs. Broadcast
- When one thread changes the value of a condition
upon which others might be waiting, the modifier
is obliged to notify these waiting threads - Always safest, though perhaps not very efficient,
to use broadcast to notify waiting threads after
a change - Question When is it safe to use signal?
21Model checking
- Technique for exhaustively and automatically
analyzing finite-state models to find bad
states - Bad states are specified in a temporal logic or
some other declarative notation - Lots of tools that can be used for this purpose
- FSP, SMV, Spin, etc
- If you are designing concurrent software, want to
learn how to use these tools
22Recall State explosion problem
- Number of states in a system with multiple,
orthogonal, behaviors grows exponentially
(product of number of states of each feature) - Major impediment to understanding
- Impossible to visualize in any meaningful way
- Requires the use of analysis tools to verify
properties - Managing state explosion
- Concurrent state machines
- Each object in a system modeled as a state
machine - Object state machine runs concurrently with those
of other objects - Concurrent composite states
- Separates one machine can into orthogonal
submachines
23Example Concurrent composite state
Automobile
Temperature control
TempOn
pushAir
Cooling
pushTCOff
TempOff
pushHeat
pushAir
Heating
pushHeat
Rear defroster
Radio control
pushRD
pushRad
RDOff
RDOn
RadOff
RadOn
pushRD
pushRad