Concurrency and Critical Sections: Preventing Race Conditions in Multithreaded Programs, Study notes of Operating Systems

The problem of concurrency in multithreaded programming and the need for mutual exclusion to prevent race conditions. It discusses various methods for achieving mutual exclusion, including shared lock variables, strict alternation, check then lock, and peterson's algorithm. The document also covers the importance of progress and the need for atomic operations.

Typology: Study notes

Pre 2010

Uploaded on 09/17/2009

koofers-user-yg3
koofers-user-yg3 🇺🇸

10 documents

1 / 8

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Maria Hybinette, UGA
CSCI 4730
Operating Systems
Synchronization
Part I
Maria Hybinette, UGA 2
Chapter 6: Process Synchronization
Part I
!Why is synchronization needed?
!Definitions:
»What are race conditions?
»What are critical sections?
»What are atomic operations?
!How are locks implemented?
Maria Hybinette, UGA 3
Why does cooperation require
synchronization?
!Example: Two threads: Maria and Tucker share an
account with shared variable balance in memory.
!Code to deposit():
!Both Maria & Tucker deposits money into account:
»Initialization: balance = 100
»Maria: deposit( 200 )
»Tucker: deposit( 10 )
void deposit( int amount )
{
balance = balance +
amount;
}
deposit:
load RegisterA, balance
add RegisterA, amount
store RegisterA, balance
!Compiled to assembly:
Which variables are
shared? Which private?
Maria Hybinette, UGA 4
Example Execution
1. Initialization: balance = 100
2. Maria: deposit( 200 )
3. Tucker: deposit( 10 )
deposit:
load RegisterA, balance
add RegisterA, amount
store RegisterA, balance
deposit (Maria):
load RegisterA, 100
add RegisterA, 200
store RegisterA, balance
deposit (Tucker):
load RegisterA, 300
add RegisterA, 10
store RegisterA, balance
Time
Memory:
balance = 100
RegisterA = 0
Memory:
balance = 100
RegisterA = 100
Memory:
balance = 100
RegisterA = 300
Memory:
balance = 300
RegisterA = 300
Memory:
balance = 300
RegisterA = 300
Memory:
balance = 300
RegisterA = 310
Memory:
balance = 310
RegisterA = 310
Maria Hybinette, UGA 5
Concurrency
!What happens if M & T deposit
concurrently?
»Assume any interleaving is possible
»No assumption about scheduler
»Observation: When a thread is interrupted
content of registers are saved (and
restored) by interrupt handlers.
deposit (Maria):
load RegisterA, balance
add RegisterA, 200
store RegisterA, balance
deposit (Tucker):
load RegisterA, balance
add RegisterA, 10
store RegisterA, balance
Time
1. Memory:
balance = 100
RegisterA = 0
1. Memory:
balance = 100
RegisterA = 0
2. Memory:
balance = 100
RegisterA = 100
2. Memory:
balance = 100
RegisterA = 100
3. Memory:
balance = 100
RegisterA = 300
3. Memory:
balance = 100
RegisterA = 110
4. Memory:
balance = 300
RegisterA = 300
4. Memory:
balance = 110
RegisterA = 110
deposit:
load RegisterA, balance
add RegisterA, amount
store RegisterA, balance
Maria Hybinette, UGA 6
Another Example
!Who wins?
pf3
pf4
pf5
pf8

Partial preview of the text

Download Concurrency and Critical Sections: Preventing Race Conditions in Multithreaded Programs and more Study notes Operating Systems in PDF only on Docsity!

Maria Hybinette, UGA

CSCI 4730

Operating Systems

Synchronization

Part I

Maria Hybinette, UGA^2

Chapter 6: Process Synchronization

Part I

! Why is synchronization needed?

! Definitions:

» What are race conditions? » What are critical sections? » What are atomic operations?

! How are locks implemented?

Maria Hybinette, UGA^3

Why does cooperation require

synchronization?

! Example: Two threads: Maria and Tucker share an

account with shared variable ‘ balance ’ in memory.

! Code to deposit() :

! Both Maria & Tucker deposits money into account:

» Initialization: balance = 100 » Maria: deposit( 200 ) » Tucker: deposit( 10 )

void deposit( int amount )

balance = balance +

amount;

deposit:

load RegisterA, balance

add RegisterA, amount

store RegisterA, balance

! Compiled to assembly:

Which variables are shared? Which private?

Maria Hybinette, UGA^4

Example Execution

1. Initialization: balance = 100 2. Maria: deposit( 200 ) 3. Tucker: deposit( 10 )

deposit:

load RegisterA, balance

add RegisterA, amount

store RegisterA, balance

deposit (Maria):

load RegisterA, 100

add RegisterA, 200

store RegisterA, balance

deposit (Tucker):

load RegisterA, 300

add RegisterA, 10

Time store RegisterA, balance

Memory:

balance = 100

RegisterA = 0

Memory:

balance = 100

RegisterA = 100

Memory:

balance = 100

RegisterA = 300

Memory:

balance = 300

RegisterA = 300

Memory:

balance = 300

RegisterA = 300

Memory:

balance = 300

RegisterA = 310

Memory:

balance = 310

RegisterA = 310

Concurrency

! What happens if M & T deposit

concurrently?

» Assume any interleaving is possible

» No assumption about scheduler

» Observation: When a thread is interrupted content of registers are saved (and restored) by interrupt handlers.

deposit (Maria):

load RegisterA, balance

add RegisterA, 200

store RegisterA, balance

deposit (Tucker):

load RegisterA, balance

add RegisterA, 10

store RegisterA, balance

Time

1. Memory:

balance = 100

RegisterA = 0

1. Memory:

balance = 100

RegisterA = 0

2. Memory:

balance = 100

RegisterA = 100

2. Memory:

balance = 100

RegisterA = 100

3. Memory:

balance = 100

RegisterA = 300

3. Memory:

balance = 100

RegisterA = 110

4. Memory:

balance = 300

RegisterA = 300

4. Memory:

balance = 110

RegisterA = 110

deposit:

load RegisterA, balance

add RegisterA, amount

store RegisterA, balance

Another Example

! Who wins?

Maria Hybinette, UGA^7

Aside: What program data is shared?

! Local variables are not shared (private)

» Each thread has its own stack

» Local variables are allocated on private stack

» Weird Bugs: Never pass, share or store a pointer to a local

variable on another threads stack

! Global variables and static objects are shared

» Stored in the static data segment, accessible by any

threads

! Dynamic objects and other heap objects are shared

» Allocated from heap with malloc / free or new / delete

Maria Hybinette, UGA^8

Race Condition

! Results depends on order of execution

» Result in non-deterministic bugs, hard to fine!

  • Deterministic : Input alone determines results, i.e., the

same inputs always produce the same results

! Intermittent - time depended `bug’ a small change

may hide the real bug (e.g., print statements can

hide the real bug because the slow down

processing and impact the timing of the threads).

Maria Hybinette, UGA^9

How to avoid race conditions

! Idea: Prohibit one or more threads from

reading and writing shared data at the same

time!! Provide Mutual Exclusion

! Critical Section: Part of program where

shared memory is accessed

void credit( int amount )

int x = 5;

printf( “Adding money” );

balance = balance + amount;

void debit( int amount )

int i;

balance = balance - amount;

for( i = 0; i < 5; i++ );

Critical Section

Maria Hybinette, UGA^10

Critical Sections

! Problem: Avoiding race conditions (i.e.,

provide mutual exclusion) is not sufficient for

having threads cooperate correctly and

efficiently

» What about if no one gets into the critical section even if several threads wants to get in? » What about if someone waits outside the critical section and never gets a turn?

Mutual Exclusion

Process Maria Process Tucker Time Maria enters her critical section Maria leaves her critical section Tucker attempts to enter his critical section Tucker is blocked, and waits Tucker enters his critical section void { deposit( int amount ) Tucker leaves his critical section balance = balance + amount; }

Critical Section Problem: Properties

Required Properties:

! Mutual Exclusion:

» Only one thread in critical section at a time

! Progress (e.g., someone gets the CS):

» Not block others out: if there are requests to enter the CS must allow one to proceed (e.g., no deadlocks). » Must not depend on threads outside critical section

  • If no one is in CS then must let someone in.

! Bounded waiting (starvation-free):

» Must eventually allow each waiting thread to enter

Maria Hybinette, UGA^19

Attempt 1: Lock Variable

Problem & Lesson

Bounded Waiting No Starvation Shared Lock Variable X Progress someone gets the CS Mutual Exclusion ! Problems:

» No mutual exclusion: Both processes entered the CS.

! Lessons: Failed because two threads read the lock variable simultaneously and both thought it was their ‘turn’ to get into the critical section

Maria Hybinette, UGA^20

Attempt 2: Strict Alternation

! Idea: Take turns. turn determines which

thread can enter (set to thread ID’s: 0 or 1 ).

! Does this work?

» Mutual exclusion?

» Progress (someone gets the CS if empty, no deadlock)?

» Bounded Waiting (no starvation)? int turn = 0; // shared variable void deposit( int amount ) { while( turn <> 1-tid ) {} /* wait */ ; balance += amount; // critical section turn = 1-tid; } Entry CS: CS: Exit CS:

Maria Hybinette, UGA^21

int turn = 0; // shared variable void deposit( int amount ) { while( turn <> 1-tid ) {} /* wait */ ; balance += amount; // critical section turn = 1-tid; }

Attempt 2: Strict Alternation

! Initialize: Maria is ‘ 0 ’ & Tucker is

! M reads turn sees her turn

! M done and change turn to other

! T never requests CS no money!

0: Process Maria 1: Process Tucker Time Tucker is not interested in CS Maria is blocking!

Maria Hybinette, UGA^22

Attempt 2: Strict Alternation

! Problems:

» No progress:

  • if no one is in a critical section and a thread wants

in -- it should be allowed to enter

» Efficiency:

  • Pace of execution: Dictated by the slower of the

two threads. IF Tucker uses its CS only one per

hour while Maria would like to use it at a rate of

1000 times per hour, then Maria has to adapt to

Tucker’s slow speed.

Strict Alteration Yes No No Bounded Waiting No Starvation Shared Lock Variable No Progress someone gets the CS Mutual Exclusion Pace limited to slowest process

Attempt 2: Strict Alternation

! Problem: Progress

! Lesson: Why did strict alternation fail?

» Pragmatically: Problem with the turn variable is that we need state information about BOTH processes.

  • We should not wait for a thread that does not need if

they don’t need to get to the critical section

! Idea:

» We need to know the needs of others! » Check to see if other needs it. Don’t get the lock until the ‘other’ is done with it.

Attempt 3: Check State then Lock

! Idea: Each thread has its own lock; lock

indexed by tid (0, 1). Check other’s needs

! Does this work? Mutual exclusion? Progress (someone gets the CS if empty, no deadlock)? Bounded Waiting (no starvation)? boolean lock[2] = {false, false} // shared void deposit( int amount ) { while( lock[1-tid] == true ) {} /* wait */ ; lock[tid] = true; balance += amount; // critical section lock[tid] = false; } Entry CS: CS: Exit CS:

Maria Hybinette, UGA^25

boolean void deposit( lock[2] int = {false,amount )false} // shared { while( lock[1-tid] == true ) {} /* wait */; lock[tid] = true; balance += amount; // critical section lock[tid] } = false;

Attempt 3: Check then Lock

! M checks if Tucker is interested and he isn’t ! T checks if Maria is interested and she isn’t ! Switch lock back to Maria she now sets his ! Switch Back to Tucker he sets his lock 0: Process Maria 1: Process Tucker Time Enter CS Enter CS

Maria Hybinette, UGA^26

Attempt 3: Check then Lock

! Problems:

» No Mutual Exclusion

! Lesson: Process lock’s the critical section AFTER the process has checked it is available but before it enters the section. ! Idea: Lock the section first! then lock Check then Lock No Strict Alteration Yes No No Bounded Waiting No Starvation Shared Lock Variable No Progress someone gets the CS Mutual Exclusion Pace limited to slowest process

Maria Hybinette, UGA^27

Attempt 4: Lock then Check

! Idea: Each thread has its own lock; lock

indexed by tid (0, 1). Check other’s needs

! Does this work? Mutual exclusion? Progress (someone gets the CS if empty, no deadlock)? Bounded Waiting (no starvation)? boolean lock[2] = {false, false} // shared void deposit( int amount ) { lock[tid] = true; while( lock[1-tid] == true ) {} /* wait */ ; balance += amount; // critical section lock[tid] = false; } Entry CS: CS: Exit CS:

Maria Hybinette, UGA^28

boolean lock[2] = {false, false} // shared void { deposit( int amount ) lock[tid] = true; while( lock[1-tid] == true ) {} /* wait */; balance += amount; // critical section lock[tid] } = false;

Attempt 4: Lock then Check

Mutual Exclusion?

! Maria’s View: Once Maria sets her

lock:

» Tucker cannot enter until Maria is done » Tucker blocks alreadyuntil Tucker in CS, leaves then theMaria CS

! Tucker’s View: Same thing

! So yes Mutual Exclusion

Time 0: Process Maria 1: Process Tucker boolean lock[2] = {false, false} // shared void { deposit( int amount ) lock[tid] = true; while( lock[1-tid] == true ) {} /* wait */; balance += amount; // critical section lock[tid] } = false;

Attempt 4: Lock then Check

! Mutual Exclusion: Yes

! Deadlocks: Each thread waits for the

other. Each one thinks that the other

is in the critical section

Time 0: Process Maria 1: Process Tucker Maria waits for Tucker Tucker waits for Maria

Attempt 4: Lock then Check

! Problems:

» No one gets the critical section!

» Each thread ‘insisted’ on its right to get the CS and did

not back off from this position.

! Lesson: Again a ‘state’ problem, a thread

misunderstood the state of the other thread

! Idea: Allow a thread to back off to give the other a

chance to enter its critical section.

Lock then Check Yes No (deadlock) Check then Lock No Strict Alteration Yes No No Bounded Waiting No Starvation Shared Lock Variable No Progress someone gets the CS Mutual Exclusion Pace limited to slowest process

Maria Hybinette, UGA^37

Attempt 6: Dekker’s Algorithm

! Take ‘careful’ turns

boolean lock[2] = {false, false} // shared int turn = 0; // shared variable void deposit( int amount ) { lock[tid] = true; while( lock[1-tid] == true ) // check other { if( turn == 1-tid ) // other turn to insist lock[tid] = false; // then I defer while( turn == 1 - tid ) {}; lock[tid] = true; } balance += amount; // critical section turn = 1 - tid; lock[tid] = false; }

Maria Hybinette, UGA^38

Attempt 7: Peterson’s Algorithm

! Idea: also combines turn and separate locks ! When 2 processes enters simultaneously, setting turn to the other releases the ‘other’ process from the while loop (one write will be last). ! Mutual Exclusion: Key Observation: turn cannot be both 0 and 1 at the same time boolean lock[2] = {false, false} // shared int turn = 0; // shared variable void deposit( int amount ) { lock[tid] = true; turn = 1-tid; while( lock[1-tid] == true && turn == 1-tid ) {}; balance += amount; // critical section lock[tid] = false; }

Maria Hybinette, UGA^39

Peterson’s Algorithm Intuition

! Mutual exclusion: Enter critical section if and only if

» Other thread does not want to enter

» Other thread wants to enter, but your turn

! Progress: Both threads cannot wait forever at while() loop

» Completes if other process does not want to enter

» Other process (matching turn) will eventually finish

! Bounded waiting

» Each process waits at most one critical section

boolean int turn lock[2] = 0; // = shared{false, variable false} // shared void { deposit( int amount ) lock[tid] = true; turn while( = 1-tid;lock[1-tid] == true && turn == 1-tid ) {}; balance lock[tid] += = amount;false; // critical section }

Maria Hybinette, UGA^40

Summary: Software Solutions

Peterson Yes Yes Yes Dekker Yes Yes Yes Deferral Yes (not deadlock)^ No Not really Lock then Check Yes No (deadlock) Check then Lock No Strict Alteration Yes No No Bounded Waiting No Starvation Shared Lock Variable No Progress someone gets the CS Mutual Exclusion Pace limited to slowest process Simpler

Lamport’s Bakery Algorithm

! Idea: Bakery -- each thread picks next highest ticket (may have ties) ! Enter critical section when have lowest ticket ! Data Structures (size N):

» choosing[i] : true iff Pi in the entry protocol

» number[i] : value of ‘ticket’, one more than max

» Threads may share the same number

! Ticket is a pair: ( number[tid], i ) ! Lexicographical order:

» (a, b) < (c, d) :

if( a < c) or if( a == c AND b < d )

» (number[j],j) < (number[tid],tid))

Bakery Algorithm

choosing[tid] = true;

number[tid] = max( number[0], … , number[n-1] ) + 1;

choosing[tid] = false;

for(j = 0; j < n; j++)

while( choosing[j] ){}; // wait until j is done choosing

// wait until number[j] = 0 (not interested) or

// my number is the lowest

while( number[j]!= 0 && ( (number[j],j) < (number[tid],tid))

balance += amount;

number[tid] = 0;

! Pick next highest ticket (may have ties) ! Enter CS when my ticket is the lowest

Maria Hybinette, UGA^43

Baker’s Algorithm Intuition

! Mutual exclusion:

» Only enters CS if thread has smallest number

! Progress:

» Entry is guaranteed, so deadlock is not possible

! Bounded waiting

» Threads that re-enter CS will have a higher number than threads

that are already waiting, so fairness is ensured (no starvation)

choosing[tid] = true; number[tid] = max( number[0], … , number[n-1] ) + 1; choosing[tid] = false; for(j = 0; j < n; j++) while( choosing[j] ){}; // wait until j is done choosing // wait until number[j] = 0 (not interested) or me smallest number while( number[j]!= 0 && ( (number[j],j) < (number[tid],tid)) ); balance += amount; number[tid] = 0;