Lecture-6 Synchronization
Lecture-6 Synchronization
1
Before we start …: Exercise # 1
count is a global variable initialized to 5
Thread 1 Thread 2
void foo() void bar()
{ {
count++; count--;
} }
2
Before we start …: Exercise # 1
• count++ could be implemented as: • count-- could be implemented as:
register1 = count register2 = count
register1 = register1 + 1 register2 = register2 – 1
count = register1 count = register2
• On most machines, memory references and assignments (i.e., loads and stores)
of words are atomic
• Consequently – weird example that produces “4” on previous slide can’t happen
5
Shared Resources
• We initially focus on controlling access to shared resources
• Basic Problem:
• If two concurrent threads (processes) are accessing a shared variable, and that
variable is read/modified/written by those threads, then access to the variable must
be controlled to avoid erroneous behavior
• Suppose that you and your significant other share a bank account with a
balance of $1000.
• Then you each go to separate ATM machine and simultaneously withdraw $100 7
from the account.
Classic Example: Bank Account Balance
• We’ll represent the situation by creating a separate thread for each person to
do the withdrawals
balance = get_balance(account);
balance = balance – amount;
Execution balance = get_balance(account);
sequence balance = balance – amount; Context Switch
seen by CPU
put_balance(account, balance);
put_balance(account, balance);
12
Mutual Exclusion
• We want to use mutual exclusion to synchronize access to shared resources
allowing us to have larger atomic blocks
• Code that uses mutual exclusion to synchronize its execution is called a critical
section.
• The Critical Section Problem – ensure that when one thread is executing in
its critical section, no other thread is allowed to execute in its critical section.
while (TRUE) {
while (TRUE) {
entry section
critical section
critical section
remainder section
exit section 13
}
remainder section
Entry section will allow only one process to enter and
}
execute critical section code.
Critical-Section Requirements
• Mutual Exclusion – If thread Ti is executing in its critical section, then no other
threads can be executing in their critical sections
• Progress – If some thread T is not in the critical section, then T cannot prevent
some other thread S from entering the critical section. A thread in the critical
section will eventually leave it.
• Bounded Waiting – If some thread is waiting on the critical section, then T will
eventually enter the critical section (i.e., no starvation).
• Assume that each process executes at a nonzero speed
• No assumption concerning relative speed of the n processes
15
“Too Much Milk” Problem
• What are the correctness properties for the “Too much milk” problem???
• Only one person buys milk at a time
• Someone buys milk if you need it
• Restrict ourselves to use only atomic load and store operations as building blocks
16
“Too Much Milk” Problem: Solution #1
• Leave a note before buying (a version of “lock”)
• Remove note after buying (a version of “unlock”)
• Don’t buy any milk if there is note (wait)
• Suppose a computer tries this (remember, only memory read/write are atomic):
Thread A Thread B
if (noMilk & NoNote){ if (noMilk & NoNote){
leave Note; leave Note;
buy milk; buy milk;
remove note; remove note;
} }
• Does it work?
• Still too much milk but only occasionally!
17
• Thread context switched after checking milk & note but before buying milk!
• Solution makes problem worse since fails intermittently
“Too Much Milk” Problem: Solution #2
• How about using labeled notes so we can leave note before checking the milk?
Thread A Thread B
leave note A; leave note B;
if (noNote B) { if (noNoteA){
if (noMilk){ if (noMilk){
buy Milk; buy Milk;
} }
} }
remove note A; remove note B;
• Does it work?
• Possible for neither thread to buy milk
• Context switches at exactly the wrong times can lead each to think that the other is
going to buy
• Extremely unlikely that this would happen, but will at worse possible time 18
• This kind of lockup is called “starvation!”
“Too Much Milk” Problem: Solution #3
Thread A Thread B
leave note A; leave note B;
X: while (note B){ Y: if (noNote A){
do nothing; if (noMilk){
} buy milk;
if (noMilk){ }
buy milk; }
} remove note B;
remove note A;
• Does it work?
• Yes. Both can guarantee that it is safe to buy, or other will buy, ok to quit
• At point X, either there is a note B or not:
• if no note B, safe for A to buy since B has either not started or quit
• otherwise A waits until there is no longer a note B, and either finds milk that B has
bought or buys it if needed
• At point Y either there is a note A or not:
19
• if no note A, safe for B to check & buy milk (Thread A not started yet)
• Otherwise, A is either checking & buying milk or waiting for B to quit, so B quits by
removing note B.
“Too Much Milk” Problem: Solution #3
• Is Solution #3 a good solution?
• Semaphores: Basic, easy to get the hang of, but hard to program with
21
Mutex with Atomic R/W: Try # 1
int turn = 0;
T0 T1
Exit Section
• Does it satisfy the mutual exclusion requirement?
23
• Does it satisfy the progress requirement?
Mutex with Atomic R/W: Peterson’s
Algorithm
• A two-process solution
• Assume that the load and store machine-language instructions are atomic; that
is, cannot be interrupted
• The two processes share two variables:
• 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.
• Choosing a number
• max (a0,…, an-1) is a number k, such that k ≥ ai for i = 0, …, n – 1
while (TRUE) {
choosing[i] = TRUE; // Process i is choosing a number
number[i] = 1 + max(number[0], … , number[n – 1]);
choosing[i] = FALSE; // Process i has chosen its number
28
Using Locks
acquire(lock);
balance = get_balance(account);
balance = balance – amount;
withdraw (account, amount) {
acquire(lock); acquire(lock);
balance = get_balance(account);
balance = balance – amount; Critical put_balance(account, balance);
put_balance(account, balance); Section release(lock);
release(lock); balance = get_balance(account);
return balance; balance = balance – amount;
}
put_balance(account, balance);
release(lock);
struct lock {
int held = 0;
} busy-wait (spin wait) for
lock to be released
void acquire (lock) {
while (lock held);
lock held = 1;
} A context switch can occur
void release (lock) { here, causing a race condition
lock held = 0;
}
• This is called a spinlock because a thread spins waiting for the lock to be released
• Does this work? 31
• No. Two independent threads may both notice that a lock has been released and
thereby acquire it.
Implementing Locks
• The problem is that the implementation of locks has critical sections, too!
• Often used to implement locks. The idea is that if the value is 0 (unlocked), it
can be set to 1 (locked), and the calling process obtains the lock. If it is already 33
1, the process knows the lock is held by another.
Using Test-And-Set
• Here is the lock implementation with test-and-set:
struct lock {
int held = 0;
}
void acquire (lock) {
while (test_and_set(&lock held));
}
void release (lock) {
lock held = 0;
}
struct lock {
int held = 0;
}
void acquire (lock) {
while (compare_and_swap(&lock held, 0, 1) != 0);
}
void release (lock) {
lock held = 0;
}
• How did the lock holder give up the CPU in the first place?
• Lock holder calls yield or sleep
• Involuntary context switch
39
Disabling Interrupts
• Another implementation of acquire/release is to disable interrupts
struct lock {
}
void acquire (lock) {
disable interrupts;
}
void release (lock) {
enable interrupts;
}
Spinlocks acquire(lock)
• Threads waiting to acquire lock Disabling Interrupts
... • Disabling interrupts for long
spin in test-and-set loop
• Wastes CPU cycles Critical Section periods of time can miss or
• Longer the CS, the longer the ... delay important events (e.g.,
spin, greater the chance for lock release(lock) timer, I/O) 42
holder to be interrupted
Implementing Locks
• Block waiters, interrupts enabled in critical sections
45
Blocking in Semaphores
• Associated with each semaphore is a queue of waiting threads
46
Semaphore Types
• Mutex Semaphore (or binary Semaphore)
• Represents single access to a resource
• Guarantees mutually exclusion to a critical section
• It is initialized to free (value = 1)
47
Semaphores Implementation
• Must guarantee that no two processes can execute the wait() and
signal() on the same semaphore at the same time
• Thus, the implementation becomes the critical section problem where the
wait() and signal() code are placed in the critical section
• Busy waiting implementation is not a good solution as many applications may
spend lots of time in critical sections
• In a no-busy waiting implementation, a waiting queue is associated with each
semaphore and each entry in a waiting queue has two data items:
• Value (of type integer)
• Pointer to next record in the list
• Two operations:
• block – place the process invoking the operation on the appropriate waiting queue 48
• wakeup – remove one process from waiting queue and place it in the ready queue
Implementation With No Busy Waiting
• Waiting queue:
wait(S);
struct Semaphore {
balance = get_balance(account);
int value;
balance = balance – amount;
Queue Q;
} Threads wait(S);
withdraw (account, amount) { Block
wait(S);
wait(S);
balance = get_balance(account); put_balance(account, balance);
balance = balance – amount; Critical
Section signal(S);
put_balance(account, balance);
signal(S); ...
return balance; signal(S);
} ... 50
signal(S);
It is undefined which
thread runs after a signal
“Too Much Milk” Problem: Binary
Semaphores
• Implementing “Too Much Milk” with semaphores
Thread A Thread B
wait(S); wait(S);
if (noMilk) if (noMilk
buy milk; buy milk;
signal(S); signal(S);
51
Example: Counting Semaphores
• A library has 10 study rooms, to be used by one student at a time. At most 10
students can use the rooms concurrently. Additional students that need to use the
rooms need to wait until a room is free.
• Students must request a room from the front desk and return to the desk when
finished using a room. The clerk doesn’t keep track of which room is occupied or
who is using it. Upon a room request, the clerk decreases the count and upon room
release, the clerk increases this count. Front desk represents a semaphore, rooms
are the resources, and students represent processes. How can we code those
processes?
• Solution: One of the processes creates and initializes a semaphore to S = 10.
wait (S);
…
Each process has to be
….use one instance of the resource… 52
coded in this manner.
…
signal (S);
Using Semaphores: Other Synchronization
Problems
P0 P1
… …
S1; Assume we definitely want to
S2;
…. have S1 executed before S2.
….
semaphore x = 0; // initialized to 0
P0 P1
… …
S1; wait (x);
Solution: signal (x); S2;
…. ….
53
Semaphore Exercise
• X and Y are shared semaphores. The following 3 pseudo-coded threads are
started. What is the output and also mention the updated values of X and Y
after completion of every Thread? ? X = 0, Y = 1
Thread 1 Thread 2 Thread 3
wait(S) signal(S)
{ {
while (S ≤ 0); // busy S++; Answer:
54
wait } CAB
S--;
} X = 1, Y = 0
Semaphore Exercise
• Write a pseudo code to synchronize processes A, B, C and D by using
semaphores so that process B must finish executing before A starts, and
process A must finish before process C or D starts. Show your solution. You
should assume three semaphores X, Y and Z and all initialized to zero.
Solution:
X=Y=Z=0