0% found this document useful (0 votes)
67 views61 pages

Advanced Synchronization: - Bloom Paper (Online) - Chapter 6 From Silberschatz - Slides Have Many Illustrative Examples

- The document discusses various techniques for synchronization including hardware, operating system, and language-level support. - It describes synchronization building blocks like mutex locks, semaphores, monitors, and their usage for problems like the readers-writers problem. - Monitors encapsulate shared data and synchronize access through entry points, but only allow one process inside at a time, limiting concurrency. Later languages provide higher-level synchronization constructs.

Uploaded by

linuxraaja
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
67 views61 pages

Advanced Synchronization: - Bloom Paper (Online) - Chapter 6 From Silberschatz - Slides Have Many Illustrative Examples

- The document discusses various techniques for synchronization including hardware, operating system, and language-level support. - It describes synchronization building blocks like mutex locks, semaphores, monitors, and their usage for problems like the readers-writers problem. - Monitors encapsulate shared data and synchronize access through entry points, but only allow one process inside at a time, limiting concurrency. Later languages provide higher-level synchronization constructs.

Uploaded by

linuxraaja
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 61

Advanced Synchronization

Bloom paper (online)


Chapter 6 from Silberschatz
slides have many illustrative examples

Support for Critical Section


Hardware support: Some hardware instructions
are provided to support the programmer
(e.g., test&set, and swap instructions)

Operating System Support: Operating system


supports for the declaration of data structures and
also operations on those data structures
(e.g., semaphores)

High Level Language Support: Language


provides support for data structures and operations
on them to help with synchronization.
(e.g., critical regions, monitors, serializers, etc)

Synchronization Building Blocks


Most synchronization on symmetric multiprocessors is
based on an atomic test and set instruction in hardware
we need to do a load and store atomically
Example:
try_again:
ldstub address -> register
compare register, 0
branch_equal got_it
call go_to_sleep
jump try_again
got_it:
return

ldstub: load and store unsigned byte (SPARC)

Other kinds of atomic primitives at the hardware level may


be even more powerful
e.g., compare_and_swap(mm, reg1, reg2)
(if mm==reg1, mm=reg2)

Limits on low-level mechanism


no abstraction and modularity
i.e., a process that uses a semaphore has to
know which other processes use the semaphore,
and how these processes use the semaphore
a process cannot be written in isolation

error-prone
change order or omit lock/unlock, signal/wait

difficult to verify correctness

Higher-level constructs
Language Support
monitors, serlializers, path experssions,
conditional critical regions, RWlocks
mainly programming languages targeted for
concurrent programming and object
oriented languages: Concurrent Pascal (Path
Pascal), Concurrent C, Java, Modula, Ada,
Mesa, Eiffel,

use constructs provided by language and trust


compiler to translate them
data structures need to be created (for queues, counts,
locks, etc.)
lock/unlock and signal/wait primitives will be invoked
on the right mutex/semaphore/condition variables

at the lowest level these will most likely translate


to test&set atomic instructions or OS-supported
semaphores

Requirements of Synch Mech


modularity
separation of resource and its access operations, and
synchronizer and its mechanisms

expressive power
specifying the needed constraints (exclusion and
priority constrains in terms of relevant information)

ease of use
composing a set of constraints (for complex synch.
schemes)

modifiability
changing the constraints if needed

correctness
safety net against inadvertent user errors

Basic Mechanisms
mutex lock
condition variables
semaphores
Proposed in 1969 by Dijkstra for process synchronization
Cooperating Sequential Processes

P(S):
while S <= 0 do nothing; // busy wait
S = S - 1;

V(S):
S = S + 1;

Init_Sem(S, Count): S = Count;


enables mutual exclusion, process synch;

Semaphore concept
Integer variable, with initial non-negative value
Two atomic operations
wait
signal (not the UNIX signal() call)
p & v (proberen & verhogen), up and down, etc

wait
wait for semaphore to become positive, then decrement
by 1

signal
increment semaphore by 1

Semaphore without busy-wait


struct sem {
value: int;
L: list of processes
} S;
Init_Sem(S, Count)
{
S.value = Count;
}
OS support
block
wakeup
P and V atomic

P(S)
{S.value = S.value -1;
if (S.value < 0) {
add T to S.L;
block;
}
V(S)
{S.value = S.value + 1;
if (S.value <=0) {
select a T from S.L;
wakeup(T);
}

Mutual exclusion using


semaphores
Use wait and signal like lock and unlock
bracket critical sections in the same manner

Semaphore value should never be greater than 1


This is a binary semaphore

Depends on correct order of calls by programmer


All mutex problems apply (deadlock)
What should the initial value be?

Resource counting
A counting semaphore can take on arbitrary
positive values
max value usually system dependent but tunable

General idea

initialize semaphore value to # of resources


wait = acquire resource
signal = relinquish resource
when all resources are taken, wait will well, wait.

Default semaphore type is usually counting


. easy to make it a mutex

Monitors
Monitors are a synchronization mechanism where the shared
data are encapsulated with the locks required to access them
Variables/data local to a monitor cannot be directly accessed from
outside.

the data can only be accessed through the locked code


mon_name (ep1, ep2, , epn)
{
entry_point ep1
entry_point ep2
{
{
}
}
}

all eps are mutually exclusive


multiple threads inside the monitor?
yes, but only one active
others?
waiting on an event

...
...

Monitors ..
Silberschatz textbook 6.7. example of Monitor
implementation with semaphores (structure for each condition
x, actions for x.wait and x.signal)
Process a

Process b

Procedure i

Procedure j

ConditionVariable.wait
Queue for each
conditional
variable
Only one
process at a
given time

ConditionVariable.signal

Data

Designing a Monitor

When a process is active inside a monitor, other processes get


queued up.

Queuing up: done by operation wait. Dequeuing by signal.

Queuing/dequeuing up: more than 1 reason possible. E.g.,


waiting for a reader to depart, waiting for a writer to finish, ..

Condition variable may be associated with wait and signal.

E.g., OKtoread.wait, OKtoread.signal,

Queues: generally FIFO, priorities may be implemented with a


parameter.

signaling threads immediately relinquish control of the


monitor (in original definition)

this means they cannot signal multiple condition variables at the same
time!

Monitor-style programming
With mutexes and condition variables you can
implement any critical section
CS_enter(); [controlled code] CS_exit();
void CS_enter() {
Lock(m) {
while (![condition])
Wait(c, m)
[change shared data
to reflect in_CS]
[broadcast/signal as needed]
}
}

void CS_exit() {
Lock(m) {
[change shared data
to reflect out_of_CS]
[broadcast/signal as needed]
}
}

Readers/Writer example structure:


monitor
{ Condition OKtoread, OKtowrite;
int readercount;
// data decls
void StartRead() { ... }
void StartWrite() { ... }
void FinishRead() { ... }
void FinishWrite() { ... } }

Readers Priority: Monitors


readers-writers: monitor;
begin // the monitor
readercount: integer;
busy: boolean;
OKtoread, OKtowrite: condition;
procedure StartRead;
begin
if busy then OKtoread.wait;
readercount := readercount + 1;
OKtoread.signal; // all readers can start
end StartRead;
procedure EndRead;
begin
readercount := readercount - 1;
if readercount = 0 then OKtowrite.signal;
end EndRead;

Readers Priority: Monitors ...


procedure StartWrite;
begin
if busy OR readcount != 0 then
OKtowrite.wait;
busy := true;
end StartWrite;
procedure EndWrite;
begin
busy := false;
if OKtoread.queue then
OKtoread.signal;
else OKtowrite.signal;
end EndWrite;
begin // initialization
readercount := 0; busy := false;
end;
end readers-writers.

Readers-Writers: Monitors
Reader:
StartRead();
ReadFile();
EndRead();
Writer:
StartWrite();
WriteFile();
EndWrite();

Monitors: Drawbacks

Only one active process inside a monitor: no concurrency.


Previous example: File NOT inside monitor to allow
concurrency. -> Responsibility of readers and writers to
ensure proper synchronization.
Nested monitor calls can lead to deadlocks:

Consider monitors X and Y with procedures A and B. Let X.A call


Y.B and vice-versa
A process P calls X.A, process Q calls Y.B.
P is blocked on Y.B and Q is blocked on X.A

Responsibility of valid programs shifts to programmers,


difficult to validate correctness.
low-level explicit signalling needed, no connection
between abstract condition and signalling, signaller has to
choose which queue to signal explicit priorities

use
embed resource in mon (e.g. access to b-buffer)
problem?
all ops mutually exclusive

resource outside mon


permission to access inside mon
does not prevent resource being called directly

monitors vs. semaphores?


exercise: implement one using the other

scorecard for monitor


modularity and correctness (low)
ease of use, modifiability, expr. power (OK)

Monitors in Java
keyword synchronized can be used to identify
monitor regions (statements, methods, etc).
compiler generates monitorenter and monitorexit
bytecodes when monitor region is statements
within a method
JVM acquires and releases lock to corresponding
object (or class)
locks are recursive
single condition variable associated with object
that has monitor
notify() semantic is Signal-and-Continue, i.e. have
to verify that indeed the condition is true!

Example - pseudocode
import Utilities.*;
import Synchronization.*;
class BoundedBuffer extends MyObject {
private int size = 0;
private double[] buf = null;
private int front = 0, rear = 0, count = 0;
public BoundedBuffer(int size) {
this.size = size;
buf = new double[size];
}

public synchronized void deposit(double data) {


if (count == size) wait();
buf[rear] = data;
rear = (rear+1) % size;
count++;
if (count == 1) notify();
}
public synchronized double fetch() {
double result;
if (count == 0) wait();
result = buf[front];
front = (front+1) % size;
count--;
if (count == size-1) notify();
return result;
}
}

Producer/Concumer
Bounded Buffer with Monitor
Monitor
PBBuffer b : BBuffer; // This is an unprotected
bounded buffer
count : Integer;
empty, full : condition;
procedure Init;
begin
init(empty); init(full); init(b); count := 0;
end;

procedure Enqueue(I : Item)


begin
if count == BufferSize(b) then wait(full);
//BufferSize returns the maximum size of b
count++;
Enqueue(b, I);
signal(empty);
end;
procedure Dequeue(I : out Item)
begin
if count == 0 then wait(empty);
count--;
Dequeue(b, I);

signal(full);
end;

Dining Philosophers Problem


[this solution is not fair]:
Monitor DiningPhilosophers
HMNY : constant integer = {the number of
philosophers};
HMNY1 : constant integer = HMNY - 1;
state : array[0 .. HMNY1] of (thinking, hungry,
eating) = (thinking, .., thinking);
self : array[0 .. HMNY] of condition;
{We assume self's conditions
are initialized}
function Left(K : 0 .. HMNY1) return 0 .. HMNY1 is
{This is an operation only
used within the monitor}
begin
return (K+1) mod HMNY;
end;

function Right(K : 0 .. HMNY1) return 0 .. HMNY1 is


{This is an operation only
used within the monitor}
begin
return (K-1) mod HMNY;
end;
procedure Test(K : 0 .. HMNY1)
{This is an operation only
used within the monitor}
begin
if state[Left(K)] /= eating and
state[K] == hungry and
state[Right(K)] /= eating
then
{ state[K] = eating; signal(self[K]); }
end;

procedure Pickup(I : 0 .. HMNY1)


begin
state[I] = hungry;
Test(I);
if state[I] /= eating
then wait(self[I]);
end;
procedure PutDown(I : 0 .. HMNY1)
begin
state[I] = thinking;
Test(Left(I));
Test(Right(I));
end;

Each philosopher Pi will execute a simple loop:


loop
think;
DiningPhilosophers.PickUp(i);
eat;
DiningPhilosophers.PutDown(i);
forever;

Semaphore with Monitor

MONITOR monSemaphore;
VAR
semvalue : INTEGER;
notbusy : CONDITION;
PROCEDURE monP;
BEGIN
IF (semvalue = 0) THEN
WAITC(notbusy)
ELSE
semvalue := semvalue - 1;
END;

PROCEDURE monV;
BEGIN
IF (EMPTY(notbusy)) THEN
semvalue := semvalue + 1
ELSE
SIGNALC(notbusy);
END;
BEGIN { initialization code }
semvalue := 1;
END; // of monSemaphore
monitor

Serializers
Serializers was a mechanism proposed in 1979
to overcome some of the monitors shortcomings
more automatic, high-level mechanism
basic structure is similar to monitors

Data types
queue takes place of CVs, but there is no signal op, the wait
is replaced by enqueue
crowd

operations
enqueue (queue_name) until <predicate>
join (crowd_name) then { <stmts> }
implicitly relinquishes control of serializer

empty(crowd_name or queue_name)
resource access

Serializers enqueue (<priority>,<qname>)


until (<condition>)

join-crowd (<crowd>) then


(<body>) end

Only 1 active process

Multiple active processes

serializer vs. monitor


difference from monitor
resource ops can be inside the serializer (***)
no explicit signaling
explicit enqueuing on queues
automatic thread resumption (dequeue)
Serializers are similar to monitors with two main
differences:
they allow concurrency
they have an automatic signalling mechanism

A serializer allows access to a resource without mutual


exclusion, although the resource is inside the serializer
built in priorities:
threads on queue when condition true
threads leaving crowd
threads entering crowd

Operation semantics:
enqueue is like Wait, only the Signal happens
automatically when condition is true, the thread is at the
head of the queue, and some other thread leaves the
serializer
join_crowd leaves the serializer, executes a block
without mutual exclusion, but returns to the serializer
when the block finishes

Use of Serializers
Usual sequence of events for a thread:
enter serializer
enqueue waiting for event (if needed)
dequeue (automatic)
join crowd to start using resource
leave crowd (automatic)
exit serializer

Readers Priority: Serializers


Readerwriter: serializer
var
readq: queue; writeq: queue;
rcrowd: crowd; wcrowd: crowd;
db: database;
procedure read(k:key; var data: datatype);
begin
enqueue (readq) until empty(wcrowd);
joincrowd (rcrowd) then
data:= read-db(db[key]);
end
return (data);
end read;
procedure write(k:key, data:datatype);
begin
enqueue(writeq) until
(empty(rcrowd) AND empty(wcrowd) AND
empty(readq));
joincrowd (wcrowd) then write-db(db[key], data);
end
end write;

Readers-Writers in Serializers ...


Weak readers priority
enqueue(writeq) until
(empty(wcrowd) AND empty(rcrowd));
A writer does not wait until readq becomes empty

Writers priority
enqueue(writeq) until
(empty(wcrowd) AND empty(rcrowd));
enqueue(readq) until
(empty(wcrowd) AND empty(writeq));

Serializers: Drawbacks

More complex, may be less efficient


More work to be done by serializers
crowd : complex data structure; stores identity of
processes,
queue: count, semaphore, predicate
Assumes automatic signaling feature: test conditions of
every process at the head of every queue every time a
process comes out of a serializer.
Though it (automatic signalling) helps in avoiding
deadlocks and race conditions.

Serializers: pros and cons


Pros:
clean and powerful model addresses monitors
drawbacks
allows concurrency of encapsulated resources
automatic signaling simplifies programming

Cons
more complex so less efficient
automatic signaling requires testing conditions
every time possession of serializer is relinquished

scorecard for serializer


modularity and correctness (high); ease of use (high)
modifiability (OK); expr. power (OK)
efficiency (low)

Path Expressions
Path expressions are declarative specifications of allowed
behaviors of a concurrent program
synchronization is a mere side-effect of ordering the executions of
operations on a resource

To implement path expressions, a run-time system (path


controller for each instance of shared resource) is needed to
check the validity of orderings
it keeps track of operation start and end
it blocks threads if their execution of an operation will violate the path
expression

important: automatically unblocks when execution can go on

Path expressions do not cause the operations to be executed


and do not determine who executes the operations

Path Expressions
sync for data abstraction part of definition
path expression specifies allowed orderings
syntax: path S end;
S is an expression whose variables are the
operations on the resource, and the
operators are

; (sequencing)
+ (selection)
{ } (concurrency)
path-end (repetition)

unsynch. access to ops not in path

Operator semantic
; (sequencing) defines a obliged sequencing
order between operations.
no concurrency between operations

+ (selection) means only one of the


operations can be executed at a time
the order of executions does not matter

{ } (concurrency) means any number of


instances of the embraced operations can be
executing at a time

example
path {read} + write end
multiple readers or single writer

priority?
none

path expression does not cause op invocation


several path expressions in a module; the
ordering has to be consistent with all paths
after a legal execution, another legal execution
may follow
path open; read; close; end
sequentially one after the other
no mention of WHO executes the op in path expr.

path A end
a sequence of As (the path between path and end can be
repeated)

path {A} end


concurrent As

path {A;B}+C end


the nth B can start only after n As have finished
any C will be preceded by zero or n concurrent A;B,
where all As and Bs have finished

path {A + B};C end


the nth B can start independent of how many As have
started or finished before it
all the As and Bs that have started have to complete
before a C is allowed to go

Readers/Writer (basic):
path { read } + write end

Writes and reads interleaved (at least 1 read):


path write ; { read } end

path a + b; c end
path a + (b; c) end;
path {a} + {b} end;
path {a + b} end;
path {a; b} end;
path {(a;b) + c} end
path {a; b+c} end

Usage
Each path expression is associated with a
single resource
Path expressions are used encapsulated with
the operations for a resource:
class file {
path write ; { read } end
void write(...) { ... };
int read() { ... } }

Path Expressions in Path Pascal


Path Pascal extension of Pascal that
includes directives for specifying Path
Expressions, and uses Object
Encapsulations and Processes
to specify priorities may need to introduce
artificial operations
in Blooms paper, not just read/write but
start read/write, read/write

Readers priority (weak):


path {read} + write end
either several reads or a write

Writers priority:
path write end
path start_read + {start_write; write} end
path {start_read; read} + write end
3rd expression: no reader can execute start_read when a
writer is writing
2nd expression: a writer can start a write when a writer is
writing or when a reader is reading (start_read cannot be
concurrent with start_write, however read can)
1st expression: only one reader at a time

/* A CS for up to 2 concurrent threads */


path request_enter + exit + do_enter end
// means these are mutually exclusive
path enter end
// means that instances are exclusive
path {wait ; release ; do_enter} + do_enter
end
count = 0;
private:
wait() { }
release() { }
request_enter() {if (count >= 2) wait();}
do_enter() { count++ }
public:
enter() { request_enter(); do_enter(); }
exit() { count--; release(); }

Producer/Consumer Problem

CONST nbuf = 5;
TYPE bufrange = 1..5;
ring = OBJECT
PATH nbuf:(1:(put); 1:(get)) END;
VAR buffer : ARRAY [bufrange] OF CHAR;
inp, outp : bufrange;
ENTRY PROCEDURE put(x : CHAR);
BEGIN inp := (inp MOD nbuf) + 1;
buffer[inp] := x
END
ENTRY FUNCTION get: CHAR;
BEGIN outp := (outp MOD nbuf) + 1;
get := buffer[outp]
END
INIT;
BEGIN inp := nbuf;
outp := nbuf
END
END (* end of OBJECT *)

VAR buf : ring;


c : CHAR;
BEGIN buf.put('a');
c := buf.get
END;

Dining philosopher Problem

CONST nphilopophers = 5;
maxindex = 4; (* nphilopophers - 1 *)
TYPE diner = 0..maxindex;
VAR i: integer;
table : OBJECT
PATH maxindex:(starteating; stopeating) END;
VAR fork: ARRAY [diner] OF OBJECT
PATH 1:(pickup; putdown) END;
ENTRY PROCEDURE pickup; BEGIN END;
ENTRY PROCEDURE putdown; BEGIN END;
END;
ENTRY PROCEDURE starteating(no: diner);
BEGIN fork[no].pickup;
fork[(no+1) MOD nphilsophers].pickup;
END;
ENTRY PROCEDURE stopeating(no: diner);
BEGIN fork[no].putdown;
fork[(no+1) MOD nphilsophers].putdown;
END;
END; (* table *)

PROCESS philsopher (mynum: diner);


BEGIN REPEAT

delay(rand(seed));
table.starteating(mynum);
delay(rand(seed));
table.stopeating(mynum);
UNTIL FALSE; END;
(* main *)
BEGIN

FOR i:= TO maxindex DO philsopher(i)


END.

Comments on Path Expressions


Path expressions can be complex
they may require the addition of artificial operations
it is not clear that a specification is correct when it
spans several different resources (each with its own
path expressions and operations depending on each
other)

But the specifications are declarative and


centralized (i.e., there is no need to look through
the code to find the synchronization primitives)
For synchronization on a single resource, path
expressions may be fine

scorecard
expressive power, modularity (high)
priority constraints (low)
correctness (depends)

ReaderWriter Locks
Abstraction in Java, MS .NET, other places
Manages read and write locks so you dont
have to
Also handles scheduling of blocked threads
Java implements WriterPreference,
ReaderPreference classes which implement
ReadWriteLock interface

RWLocks
Bracket critical sections as with normal mutexes
You say whether youre locking for read or write
it does the right thing
Downgrade/upgrade
reduce overhead when switching from read to write
priority over those with no locks when upgrading

Very useful for caches


or anywhere readers >> writers Java Swing GUI
threads, etc

For small critical sections, it may be overkill


need larger CS to get benefit of concurrent readers

Conditional Critical Regions


Silverschatz textbook 6.6 (and implementation with
semaphores)
var v: shared t
region v when {Condition} do Statements

the developer does not have to program the


semaphore or alternate synchronization explicitly
the compiler ``automatically' plugs in the
synchronization code using predefined libraries
once done carefully, reduces likelihood of mistakes in
designing the delicate synchronization code

Synchronization support for concurrency


problems using programming languages
monitors (shared memory support)
Concurrent Pascal (Hansen), Modula (Wirth), Mesa
(Xerox), uC++ (Buhr), Emerald (Raj)

path expressions (shared memory support)


Path Pascal (Habermann and Campbell)

message passing (non-shared memory support)


CSP: Communicating Sequential Processes (Hoare)

serializers
Eifel

RPC/rendezvous (non-shared memory support)


Ada for rendezvous

Lock free synchronization


basic idea
if mostly just read operations, and no updates,
then dont lock
instead:
catch update/write operations
allow to perform a change in a separate copy
wait until all current reads complete (or something
similar) then apply the update

You might also like