0% found this document useful (0 votes)
13 views

05 Sync Part1

pioko

Uploaded by

epinipi
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

05 Sync Part1

pioko

Uploaded by

epinipi
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 99

Processes Synchronization - Part I

Amir H. Payberah
[email protected]
Nov. 14, 2023
Processes Synchronization

1 / 46
Background

I Processes can execute concurrently.

2 / 46
Background

I Processes can execute concurrently.

https://tinyurl.com/2yjcpx75

2 / 46
Background

I Processes can execute concurrently.


I Concurrent access to shared data may result in data inconsistency.

https://tinyurl.com/2yjcpx75

2 / 46
Background

I Processes can execute concurrently.


I Concurrent access to shared data may result in data inconsistency.
I Maintaining data consistency requires mechanisms to ensure the orderly
execution of cooperating processes.

https://tinyurl.com/2yjcpx75

2 / 46
Producer-Consumer Problem

I The producer-consumer problem.

3 / 46
Producer-Consumer Problem

I The producer-consumer problem.


I Having an integer counter that keeps track of the number of items in the
buffers.

3 / 46
Producer-Consumer Problem

I The producer-consumer problem.


I Having an integer counter that keeps track of the number of items in the
buffers.
• Initially, counter is set to 0.

3 / 46
Producer-Consumer Problem

I The producer-consumer problem.


I Having an integer counter that keeps track of the number of items in the
buffers.
• Initially, counter is set to 0.
• The producer produces a new item: increment the counter

3 / 46
Producer-Consumer Problem

I The producer-consumer problem.


I Having an integer counter that keeps track of the number of items in the
buffers.
• Initially, counter is set to 0.
• The producer produces a new item: increment the counter
• The consumer consumes an item: decrement the counter

3 / 46
Producer

I 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++;
}

4 / 46
Consumer

I Consumer

while (true) {
while (counter == 0); /* do nothing */

next_consumed = buffer[out];

out = (out + 1) % BUFFER_SIZE;

counter--;
/* consume the item in next consumed */
}

5 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1

6 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1
I counter-- could be implemented as
register2 = counter
register2 = register2 - 1
counter = register2

6 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1
I counter-- could be implemented as
register2 = counter
register2 = register2 - 1
counter = register2
I Consider this execution interleaving with count = 5 initially:

6 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1
I counter-- could be implemented as
register2 = counter
register2 = register2 - 1
counter = register2
I Consider this execution interleaving with count = 5 initially:
S0: producer: register1 = counter: register1 = 5

6 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1
I counter-- could be implemented as
register2 = counter
register2 = register2 - 1
counter = register2
I Consider this execution interleaving with count = 5 initially:
S0: producer: register1 = counter: register1 = 5
S1: producer: register1 = register1 + 1: register1 = 6

6 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1
I counter-- could be implemented as
register2 = counter
register2 = register2 - 1
counter = register2
I Consider this execution interleaving with count = 5 initially:
S0: producer: register1 = counter: register1 = 5
S1: producer: register1 = register1 + 1: register1 = 6
S2: consumer: register2 = counter: register2 = 5

6 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1
I counter-- could be implemented as
register2 = counter
register2 = register2 - 1
counter = register2
I Consider this execution interleaving with count = 5 initially:
S0: producer: register1 = counter: register1 = 5
S1: producer: register1 = register1 + 1: register1 = 6
S2: consumer: register2 = counter: register2 = 5
S3: consumer: register2 = register2 - 1: register2 = 4

6 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1
I counter-- could be implemented as
register2 = counter
register2 = register2 - 1
counter = register2
I Consider this execution interleaving with count = 5 initially:
S0: producer: register1 = counter: register1 = 5
S1: producer: register1 = register1 + 1: register1 = 6
S2: consumer: register2 = counter: register2 = 5
S3: consumer: register2 = register2 - 1: register2 = 4
S4: producer: counter = register1: counter = 6

6 / 46
Race Condition

I counter++ could be implemented as


register1 = counter
register1 = register1 + 1
counter = register1
I counter-- could be implemented as
register2 = counter
register2 = register2 - 1
counter = register2
I Consider this execution interleaving with count = 5 initially:
S0: producer: register1 = counter: register1 = 5
S1: producer: register1 = register1 + 1: register1 = 6
S2: consumer: register2 = counter: register2 = 5
S3: consumer: register2 = register2 - 1: register2 = 4
S4: producer: counter = register1: counter = 6
S5: consumer: counter = register2: counter = 4

6 / 46
What’s The Output?

int counter = 0;

void* thread_func(void *arg) {


counter++;
printf("Job %d started.\n", counter);
sleep(2);
printf("Job %d finished.\n", counter);

return NULL;
}

int main(void) {
pthread_t t1, t2;

pthread_create(&t1, NULL, &thread_func, NULL);


pthread_create(&t2, NULL, &thread_func, NULL);

pthread_join(t1, NULL);
pthread_join(t2, NULL);

return 0;
}

7 / 46
What’s The Output?

Job 1 started.
Job 2 started.
Job 2 finished.
Job 2 finished.

8 / 46
The Critical-Section (CS)
Problem

9 / 46
The Critical-Section Problem (1/2)

I Consider system of n processes {p0 , p1 , · · · , pn−1 }.

10 / 46
The Critical-Section Problem (1/2)

I Consider system of n processes {p0 , p1 , · · · , pn−1 }.

I Each process has CS segment of code.

10 / 46
The Critical-Section Problem (1/2)

I Consider system of n processes {p0 , p1 , · · · , pn−1 }.

I Each process has CS segment of code.


• Process may be changing common variables, updating table, writing file, etc.

10 / 46
The Critical-Section Problem (1/2)

I Consider system of n processes {p0 , p1 , · · · , pn−1 }.

I Each process has CS segment of code.


• Process may be changing common variables, updating table, writing file, etc.
• When one process in CS, no other may be in its CS.

10 / 46
The Critical-Section Problem (2/2)

I Each process must ask permission to enter CS in entry section, may follow
CS with exit section, then remainder section.

11 / 46
The Critical-Section Problem (2/2)

I Each process must ask permission to enter CS in entry section, may follow
CS with exit section, then remainder section.
I General structure of process Pi is below:

11 / 46
CS Problem Solution Requirements (1/3)

I Mutual Exclusion: if process Pi is executing in its CS, then no other


processes can be executing in their CSs.

12 / 46
CS Problem Solution Requirements (2/3)

I Progress: if no process is executing in its CS and there exist some pro-


cesses that wish to enter their CS, then the selection of the processes
that will enter the CS next cannot be postponed indefinitely.

13 / 46
CS Problem Solution Requirements (3/3)

I Bounded Waiting: a bound must exist on the number of times that


other processes are allowed to enter their CSs after a process has made
a request to enter its CS and before that request is granted.

14 / 46
CS Solutions

I Peterson’s solution

I Mutex lock

I Semaphore

15 / 46
Peterson’s Solution

16 / 46
Peterson’s Solution

I Two-process solution.

17 / 46
Peterson’s Solution

I Two-process solution.
I The two processes share two variables:
• int turn
• boolean flag[2]

17 / 46
Peterson’s Solution

I Two-process solution.
I The two processes share two variables:
• int turn
• boolean flag[2]

I turn: indicates whose turn it is to enter the CS.

17 / 46
Peterson’s Solution

I Two-process solution.
I The two processes share two variables:
• int turn
• boolean flag[2]

I turn: indicates whose turn it is to enter the CS.

I flag: indicates if a process is ready to enter the CS, i.e.,


flag[i] = true implies that process Pi is ready.

17 / 46
Algorithm for Process Pi

18 / 46
CS Requirements

I Provable that the three CS requirement are met:

19 / 46
CS Requirements

I Provable that the three CS requirement are met:


1. Mutual exclusion is preserved:
Pi enters CS only if: either flag[j] = false or turn = i

19 / 46
CS Requirements

I Provable that the three CS requirement are met:


1. Mutual exclusion is preserved:
Pi enters CS only if: either flag[j] = false or turn = i
2. Progress requirement is satisfied.

19 / 46
CS Requirements

I Provable that the three CS requirement are met:


1. Mutual exclusion is preserved:
Pi enters CS only if: either flag[j] = false or turn = i
2. Progress requirement is satisfied.
3. Bounded-waiting requirement is met.

19 / 46
Mutex Locks

20 / 46
Mutex Locks

I Protect a CS by first acquire() a lock then release() the lock.


• Boolean variable indicating if lock is available or not.

21 / 46
Mutex Locks

I Protect a CS by first acquire() a lock then release() the lock.


• Boolean variable indicating if lock is available or not.

I Calls to acquire() and release() must be atomic.


• Usually implemented via hardware atomic instructions.

21 / 46
Mutex Locks

I Protect a CS by first acquire() a lock then release() the lock.


• Boolean variable indicating if lock is available or not.

I Calls to acquire() and release() must be atomic.


• Usually implemented via hardware atomic instructions.

I But this solution requires busy waiting.


• This lock therefore called a spinlock.

21 / 46
acquire() and release()

acquire() {
while (!available); /* busy wait */

available = false;
}

release() {
available = true;
}

22 / 46
23 / 46
pthread Mutexes

I Mutexes are represented by the pthread mutex t object.

24 / 46
pthread Mutexes

I Mutexes are represented by the pthread mutex t object.

I pthread mutex lock() locks (acquires) a pthreads mutex.

int pthread_mutex_lock(pthread_mutex_t *mutex);

24 / 46
pthread Mutexes

I Mutexes are represented by the pthread mutex t object.

I pthread mutex lock() locks (acquires) a pthreads mutex.

int pthread_mutex_lock(pthread_mutex_t *mutex);

I pthread mutex unlock() unlocks (releases) a pthreads mutex.

int pthread_mutex_unlock(pthread_mutex_t *mutex);

24 / 46
What’s The Output?
int counter = 0;
pthread_mutex_t lock;

void* thread_func(void *arg) {


pthread_mutex_lock(&lock);
counter++;
printf("Job %d started.\n", counter);
sleep(2);
printf("Job %d finished.\n", counter);
pthread_mutex_unlock(&lock);

return NULL;
}

int main(void) {
pthread_t t1, t2;

pthread_mutex_init(&lock, NULL);

pthread_create(&t1, NULL, &thread_func, NULL);


pthread_create(&t2, NULL, &thread_func, NULL);

pthread_join(t1, NULL);
pthread_join(t2, NULL);

pthread_mutex_destroy(&lock);

return 0;
}

25 / 46
What’s The Output?

Job 1 started.
Job 1 finished.
Job 2 started.
Job 2 finished.

26 / 46
Semaphores

27 / 46
Semaphore

I Synchronization tool that provides more sophisticated ways (than Mutex


locks) for process to synchronize their activities.

28 / 46
Semaphore

I Synchronization tool that provides more sophisticated ways (than Mutex


locks) for process to synchronize their activities.

I Semaphore S: integer variable.

I Accessed via two atomic operations: wait() and signal()

28 / 46
wait() and signal()

wait(S) {
while (S <= 0); // busy wait

S--;
}

signal(S) {
S++;
}

29 / 46
Counting and Binary Semaphore

I Counting semaphore: integer value can range over an unrestricted domain.

30 / 46
Counting and Binary Semaphore

I Counting semaphore: integer value can range over an unrestricted domain.

I Binary semaphore: integer value can range only between 0 and 1.


• Same as a mutex lock.

30 / 46
Semaphore Usage (1/2)

I Initialize the semaphore to the number of available resources.

31 / 46
Semaphore Usage (1/2)

I Initialize the semaphore to the number of available resources.

I Call wait() before using a resource.

31 / 46
Semaphore Usage (1/2)

I Initialize the semaphore to the number of available resources.

I Call wait() before using a resource.

I Call signal() after releasing a resource.

31 / 46
Semaphore Usage (1/2)

I Initialize the semaphore to the number of available resources.

I Call wait() before using a resource.

I Call signal() after releasing a resource.

I If S = 0: all resources are used, and processes that wish to use a resource
will block until the count becomes greater than 0.

31 / 46
Semaphore Usage (2/2)

I Consider P1 and P2 that require C1 to happen before C2.

32 / 46
Semaphore Usage (2/2)

I Consider P1 and P2 that require C1 to happen before C2.

I Create a semaphore S initialized to 0.

// Process P1
C1;
signal(S);

// Process P2
wait(S);
C2;

32 / 46
Semaphore Usage (2/2)

I Consider P1 and P2 that require C1 to happen before C2.

I Create a semaphore S initialized to 0.

// Process P1
C1;
signal(S);

// Process P2
wait(S);
C2;

I The implementation still suffers from busy waiting.

32 / 46
Semaphore Implementation with no Busy Waiting (1/2)

I With each semaphore there is an associated waiting queue.

33 / 46
Semaphore Implementation with no Busy Waiting (1/2)

I With each semaphore there is an associated waiting queue.

I Each entry in a waiting queue has two data items:


• Value (of type integer).
• Pointer to next record in the list.

33 / 46
Semaphore Implementation with no Busy Waiting (1/2)

I With each semaphore there is an associated waiting queue.

I Each entry in a waiting queue has two data items:


• Value (of type integer).
• Pointer to next record in the list.

typedef struct {
int value;
struct process *list;
} semaphore;

33 / 46
Semaphore Implementation with no Busy Waiting (2/2)

I block: place the process invoking the operation on the appropriate wait-
ing queue.
I wakeup: remove one of processes in the waiting queue and place it in
the ready queue.

34 / 46
Semaphore Implementation with no Busy Waiting (2/2)

I block: place the process invoking the operation on the appropriate wait-
ing queue.
I wakeup: remove one of processes in the waiting queue and place it in
the ready queue.
wait(semaphore *S) {
S->value--;
if (S->value < 0) {
// add this process to S->list;
block();
}
}

34 / 46
Semaphore Implementation with no Busy Waiting (2/2)

I block: place the process invoking the operation on the appropriate wait-
ing queue.
I wakeup: remove one of processes in the waiting queue and place it in
the ready queue.
wait(semaphore *S) {
S->value--;
if (S->value < 0) {
// add this process to S->list;
block();
}
}

signal(semaphore *S) {
S->value++;
if (S->value <= 0) {
// remove a process P from S->list;
wakeup(P);
}
}

34 / 46
Deadlock

I Deadlock: two or more processes are waiting indefinitely for an event


that can be caused by only one of the waiting processes.

35 / 46
Deadlock

I Deadlock: two or more processes are waiting indefinitely for an event


that can be caused by only one of the waiting processes.

I Let S and Q be two semaphores initialized to 1.

35 / 46
Starvation

I Starvation: indefinite blocking.

36 / 46
Starvation

I Starvation: indefinite blocking.

I A process may never be removed from the semaphore queue in which it


is suspended.

36 / 46
Starvation

I Starvation: indefinite blocking.

I A process may never be removed from the semaphore queue in which it


is suspended.

I If we remove processes from the list associated with a semaphore in LIFO


(last-in, first-out) order.

36 / 46
37 / 46
POSIX Semaphore

I sem open() creates a new semaphore or opens an existing one.

sem_t *sem_open(const char * name , int oflag , ...);

38 / 46
POSIX Semaphore

I sem open() creates a new semaphore or opens an existing one.

sem_t *sem_open(const char * name , int oflag , ...);

I sem wait() decrements the value of the semaphore.

int sem_wait(sem_t *sem);

38 / 46
POSIX Semaphore

I sem open() creates a new semaphore or opens an existing one.

sem_t *sem_open(const char * name , int oflag , ...);

I sem wait() decrements the value of the semaphore.

int sem_wait(sem_t *sem);

I sem post() increments the value of the semaphore.

int sem_post(sem_t *sem);

38 / 46
Parent-Child Example

void parent() {
sem_t *sem_id = sem_open(sem_name, O_CREAT, 0600, 0);

// The parent waits for its child to print


sem_wait(sem_id);
printf("Parent: Child Printed!\n");
sem_close(sem_id);
sem_unlink(sem_name);
}

39 / 46
Parent-Child Example

void parent() {
sem_t *sem_id = sem_open(sem_name, O_CREAT, 0600, 0);

// The parent waits for its child to print


sem_wait(sem_id);
printf("Parent: Child Printed!\n");
sem_close(sem_id);
sem_unlink(sem_name);
}

void child() {
sem_t *sem_id = sem_open(sem_name, O_CREAT, 0600, 0);

printf("Child: Hello parent!\n");


sem_post(sem_id);
}

39 / 46
Readers and Writers Problem

40 / 46
Readers and Writers Problem (1/3)

I A shared data set among a number of concurrent processes:


• Readers: only read the data set; they do not perform any updates.
• Writers: can both read and write.

41 / 46
Readers and Writers Problem (1/3)

I A shared data set among a number of concurrent processes:


• Readers: only read the data set; they do not perform any updates.
• Writers: can both read and write.

I Problem: allow multiple readers to read at the same time, only one single
writer can access the shared data at the same time.

41 / 46
Readers and Writers Problem (1/3)

I A shared data set among a number of concurrent processes:


• Readers: only read the data set; they do not perform any updates.
• Writers: can both read and write.

I Problem: allow multiple readers to read at the same time, only one single
writer can access the shared data at the same time.

I Shared Data
• Semaphore rw mutex initialized to 1.
• Semaphore mutex initialized to 1.
• Integer read count initialized to 0.

41 / 46
Readers and Writers Problem (2/3)

I The writer process.

do {
wait(rw_mutex);
...
/* writing is performed */
...
signal(rw_mutex);
} while (true);

42 / 46
Readers and Writers Problem (3/3)

I The reader process.

do {
wait(mutex);
read_count++;
if (read_count == 1)
wait(rw_mutex);
signal(mutex);
...
/* reading is performed */
...
wait(mutex);
read_count--;
if (read_count == 0)
signal(rw_mutex);
signal(mutex);
} while (true);

43 / 46
Summary

44 / 46
Summary

I Access to shared data

45 / 46
Summary

I Access to shared data

I The critical-section problem

45 / 46
Summary

I Access to shared data

I The critical-section problem

I Requirements: mutual-exclusion, progress, bounding waiting

45 / 46
Summary

I Access to shared data

I The critical-section problem

I Requirements: mutual-exclusion, progress, bounding waiting


I CS solutions:
• Peterson solution, mutex lock, semaphore

45 / 46
Summary

I Access to shared data

I The critical-section problem

I Requirements: mutual-exclusion, progress, bounding waiting


I CS solutions:
• Peterson solution, mutex lock, semaphore

I Reader/writer problem

45 / 46
Questions?
Acknowledgements
Some slides were derived from Avi Silberschatz slides.

46 / 46

You might also like