[os] Pthread: Synchronization, Deadlock , Semaphore
Synthronization
- mutual exclusion:
"dekker's algorithm": Dekker's algorithm is the first known correct solution to the mutual exclusion problem in concurrent programming.
/* global or shared memory */
int x = 5;
int needLockT1 = 0; /* 0 or 1 */
int needLockT2 = 0; /* 0 or 1 */
int turn = T1; /* T1 or T2 */
/* Thread T1 */ /* Thread T2 */
while ( 1 ) while ( 1 )
{ {
execNonCriticalSection(); execNonCriticalSection();
needLockT1 = 1; needLockT2 = 1;
turn = T2; turn = T1; // compete for the 'turn'
while ( turn == T2 && while ( turn == T1 &&
needLockT2 == 1 ) needLockT1 == 1 )
{ {
/* busy wait */ /* busy wait */
} }
execCriticalSection(); execCriticalSection();
needLockT1 = 0; needLockT2 = 0;
} }
T1: needLockT1 = 1;
T1: turn = T2;
T2: needLockT2 = 1;
T2: turn = T1;
T1: execCriticalSection();
T1: needLockT1 = 0;
T2: execCriticalSection();
Semaphore:
"a system of sending messages by holding the arms or two flags or poles in certain positions according to an alphabetic code."
-- an OS construct that enables us to have synchronized access
to one or more shared resources
-- special non-negative int variable
-- two operations:
(1) first operation essentially attempts to gain access
P() proberen (to try)
wait()
down()
(2) second operation relinquishes the access the acquired
V() vrijgeven (to release)
signal()
up()
semaphore S is non-negative int variable
P( semaphore S ) /* this P() operation MUST execute without */
{ /* any interruption, i.e., no context switch */
while ( S == 0 ) /* resource count*/ /* between exiting the while() loop and */
{ /* executing S-- */
/* busy wait */
}
S--;
}
V( semaphore S )
{
S++;
}
- the "producer/consumer" problem (a.k.a. shared buffer problem)
-- Given a shared buffer (i.e., array) of a fixed size n
-- One or more producer threads
-- One or more consumer threads
/* shared/global memory */
int n = 20;
buffer[n];
semaphore empty_slots = n;
semaphore used_slots = 0;
semaphore mutex = 1;
/* producer */ /* consumer */
while ( 1 ) while ( 1 )
{ {
item = produce_next_item(); P( used_slots );
P( empty_slots ); P( mutex );
P( mutex ); item = remove_from_buffer();
add_to_buffer( item ); V( mutex );
V( mutex ); V( empty_slots );
V( used_slots ); consume( item );
} }
uses two counting semaphores to ensure:
(1) no buffer overflow will occur in a producer
(2) no reading from an empty buffer in the consumer
Uses mutex to ensure the add/remove could be exclusively happening at the same time.
"A Mutex is different than a semaphore as it is a locking mechanism while a semaphore is a signalling mechanism. A binary semaphore can be used as a Mutex but a Mutex can never be used as a semaphore."
- DINING PHILOSOPHERS PROBLEM
Given: five philosophers that engage in only two activities:
-- thinking (i.e., independent computation)
-- eating (i.e., sharing a resource; therefore, requires synchronization)
Given: shared table with five bowls and five chopsticks,
and a bowl of food in the middle of the table
(which is endlessly replenished)
Key contraint: to eat(), a philosopher must obtain two chopsticks,
one from the left, one from the right
First attempt:
chopstick is array[5] of semaphores
philosopher( i ) /* i in 0..4 */
{
while ( 1 )
{
think()
P( chopstick[i] ) //DEADLOCK
P( chopstick[i+1%5] )
eat() /* critical section */
V( chopstick[i+1%5] )
V( chopstick[i] )
}
}
Second attempt:
chopstick is array[5] of semaphores
philosopher( i ) /* i in 0..4 */
{
while ( 1 )
{
think()
P( mutex ); // top-level mutex -- NOT EFFICIENT
P( chopstick[i] )
P( chopstick[i+1%5] )
V( mutex );
eat() /* critical section */
V( chopstick[i+1%5] )
V( chopstick[i] )
}
}
Third attempt:
-- use an asymmetric solution
chopstick is array[5] of semaphores
philosopher( i ) /* i in 0..3 (instead of i in 0..4) */
{
while ( 1 )
{
think()
P( chopstick[i] )
P( chopstick[i+1%5] )
eat() /* critical section */
V( chopstick[i+1%5] )
V( chopstick[i] )
}
}
philosopher( i ) /* i is always 4 */ /*it holds the slots required as the "left" chopsticks for both its nbrs*/
{
while ( 1 )
{
think()
P( chopstick[i+1%5] ) /* we swapped the order of the P() operations */
P( chopstick[i] )
eat() /* critical section */
V( chopstick[i] )
V( chopstick[i+1%5] )
}
}
Deadlock
Deadlock: We have deadlock when no process/thread can make any
further progress (i.e., all blocked on P() operation
and the given resource will NEVER become available)
Deadlock requires four conditions:
-- mutual exclusion
-- hold and wait
-- no preemption
-- circular wait -- i.e., a cycle in resource allocation graph
Deadlock:
-
"classical circular"
-
circular: P1, P2, R1, R2