Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Synchronization
Process Synchronization
Background
The Critical-Section Problem
Petersons Solution
Synchronization Hardware
Mutex Locks
Semaphores
Classic Problems of Synchronization
Monitors
Synchronization Examples
Alternative Approaches
Objectives
To introduce the critical-section problem, whose solutions can be used to ensure the consistency of shared
data
To explore several tools that are used to solve process synchronization problems
Background
Maintaining data consistency requires mechanisms to ensure the orderly execution of cooperating
processes
Producer
while (true) {
/* produce an item in next produced */
while (counter == BUFFER_SIZE) ;
/* do nothing */
buffer[in] = next_produced;
in = (in + 1) % BUFFER_SIZE;
counter++;
}
Consumer
while (true) {
while (counter == 0)
; /* do nothing */
next_consumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
counter--;
Race Condition
{register1 = 5}
{register1 = 6}
{register2 = 5}
{register2 = 4}
{counter = 6 }
{counter = 4}
Process may be changing common variables, updating table, writing file, etc
When one process in critical section, no other may be in its critical section
Each process must ask permission to enter critical section in entry section, may follow critical section with exit
section, then remainder section
Critical Section
Petersons Solution
Assume that the load and store instructions are atomic; that is, cannot be interrupted
The variable turn indicates whose turn it is to enter the critical section
The flag array is used to indicate if a process is ready to enter the critical section. flag[i] =
true implies that process Pi is ready!
Provable that
1.
2.
3.
Synchronization Hardware
test_and_set Instruction
Definition:
do {
while (test_and_set(&lock))
; /* do nothing */
/* critical section */
lock = false;
/* remainder section */
} while (true);
compare_and_swap Instruction
Definition:
Shared Boolean variable lock initialized to FALSE; Each process has a local Boolean variable key
Solution:
do {
while (compare_and_swap(&lock, 0, 1) != 0)
; /* do nothing */
/* critical section */
lock = 0;
/* remainder section */
} while (true);
Mutex Locks
Previous solutions are complicated and generally inaccessible to application
programmers
OS designers build software tools to solve critical section problem
Simplest is mutex lock
Product critical regions with it by first acquire() a lock then release() it
Boolean variable indicating if lock is available or not
Synchronization W.R.T. to
Threads
Temporal relations
A<B<C<
Absent synchronization, instructions executed by distinct threads must be considered unordered / simultaneous
Example: Example
In the beginning...
main()
Y-axis is time.
A
pthread_create()
foo()
A'
B'
C
A<B<C
A' < B'
A < A'
C == A'
C == B'
Sequences of instructions that may get incorrect results if executed simultaneously are called critical sections
(We also use the term race condition to refer to a situation in which the results depend on timing)
Mutual exclusion means not simultaneous
A < B or B < A
We dont care which
Forcing mutual exclusion between two critical section executions is sufficient to ensure correct execution
guarantees ordering
One way to guarantee mutually exclusive execution is using locks
Critical sections
is the happens-before relation
T1
T2
Possibly incorrect
T1
T2
Correct
T1
T2
Correct
read-modify-write of
Shared variable:
e.g., a disk reader thread hands off blocks to a network writer thread through a circular buffer
disk reader
thread
network
writer
thread
circular
buffer
// read
// modify
put_balance(account, balance);
// write
Now suppose that you and your partner share a bank account with a balance of $100.00
what happens if you both go to separate ATM machines, and simultaneously withdraw $10.00 from the
account?
balance -= amount;
balance -= amount;
put_balance(account, balance);
put_balance(account, balance);
Interleaved schedules
balance = get_balance(account);
balance -= amount;
Execution sequence
as seen by CPU
balance = get_balance(account);
context switch
balance -= amount;
put_balance(account, balance);
spit out cash;
put_balance(account, balance);
spit out cash;
context switch
balance -= amount;
balance -= amount;
put_balance(account, balance);
put_balance(account, balance);
Morals:
Another example
i++;
i++;
mutual exclusion
progress
if thread T is waiting on the critical section, then T will eventually enter the critical section
performance
the overhead of entering and exiting the critical section is small with respect to the work being
done within it
Spinlocks
Monitors
Messages
simple model of communication and synchronization based on (atomic) transfer of data across a channel
Locks
acquire() prevents progress of the thread until the lock can be acquired
Locks:
Example
Locks:
Example
execution
lock()
lock()
unlock()
unlock()
Two choices:
Spin
Block
(Spin-then-block)
Acquire/Release
acquire() does not return until the caller owns (holds) the lock
What happens if the calls arent paired (I acquire, but neglect to release)?
What happens if the two threads acquire different locks (I think that access to a particular shared data
structure is mediated by lock A, and you think its mediated by lock B)?
(granularity of locking)
Using locks
acquire(lock)
balance = get_balance(account);
balance -= amount;
balance = get_balance(account);
balance -= amount;
put_balance(account, balance);
critical
section
acquire(lock);
acquire(lock)
put_balance(account, balance);
release(lock);
release(lock);
balance = get_balance(account);
balance -= amount;
put_balance(account, balance);
release(lock);
spit out cash;
spit out cash;
Roadmap
The OS and/or the user-level thread package will provide some sort of efficient primitive for user programs to
utilize in achieving mutual exclusion (for example, locks or semaphores, used with condition variables)
There may be higher-level constructs provided by a programming language to help you get it right (for
example, monitors which also utilize condition variables)
But somewhere, underneath it all, there needs to be a way to achieve hardware mutual exclusion (for example,
test-and-set used to implement spinlocks)
Spinlocks
struct lock_t {
int held = 0;
}
void acquire(lock) {
while (lock->held);
lock->held = 1;
Why doesnt this
} work?
where isvoid
release(lock)
{
the race
condition?
lock->held = 0;
}
atomic instructions
test-and-set, compare-and-swap,
disable/reenable interrupts
struct lock {
int held = 0;
}
void acquire(lock) {
while(test_and_set(&lock->held));
mutual exclusion?
(at most one thread in the critical section)
}
progress? (Tvoid
outside
cannot prevent {S from entering)
release(lock)
bounded waiting?
(waiting T will
eventually enter)
lock->held
= 0;
} (low overhead (modulo the spinning part ))
performance?
Reminder of use
acquire(lock)
balance = get_balance(account);
balance -= amount;
balance = get_balance(account);
balance -= amount;
put_balance(account, balance);
critical
section
acquire(lock);
acquire(lock)
put_balance(account, balance);
release(lock);
release(lock);
balance = get_balance(account);
balance -= amount;
put_balance(account, balance);
release(lock);
spit out cash;
spit out cash;
How does a thread blocked on an acquire (that is, stuck in a test-and-set loop) yield the CPU?
if a thread is spinning on a lock, the thread holding the lock cannot make progress
(pthread_spin_t)
(pthread_mutex_t)
Insufficient on a multiprocessor
Long periods with interrupts disabled can wreak havoc with devices
Just as with spinlocks, you only want to use disabling of interrupts to build higher-level synchronization constructs
Race conditions
Informally, we say a program has a race condition (aka data race) if the result of an executing depends on timing
i.e., is non-deterministic
Typical symptoms
I run it on the same data, and sometimes it prints 0 and sometimes it prints 4
I run it on the same data, and sometimes it prints 0 and sometimes it crashes
Summary
Questions or Suggestions
Thank You!
inquiry
dishcseATyahooDOTcom &
msjATewubdDOTedu