74
1 Synchronization! Welcome to the most important part of this course!

1 Synchronization! Welcome to the most important part of this course! Welcome to the most important part of this course!

  • View
    213

  • Download
    0

Embed Size (px)

Citation preview

1

Synchronization!

Welcome to the most important part of this course!Welcome to the most important part of this course!

OperatingSystems

Paulo MarquesDepartamento de Eng. InformáticaUniversidade de [email protected]

2006

/200

7

4. Synchronization4.1 A Systems Programmer’s View

3

Disclaimer

This slides and notes are heavily based on the companion material of [Silberschatz05].The original material can be found at: http://codex.cs.yale.edu/avi/os-book/os7/slide-dir/index.html

In some cases, material from [Stallings04] may also be used. The original material can be found at: http://williamstallings.com/OS/OS5e.html

The respective copyrights belong to their owners.

4

Consider the following library call

Process A

send_to_printer(59, “What a beautiful day!”);

Process B

send_to_printer(12, “I hate going to school!”);

5

Resulting in…

Race condition: The situation where several processes access – and manipulate shared data concurrently. The result critically depends on timing of these processes, which are “racing”.

Job from user --- 59 ---What a beautiful day!-------------------------------Job from user --- 12 ---I hate going to school!-------------------------------

What I expected!

Job from user --- 59 ---Job from user --- 12 ---What I hate a beautiful day!-------------------------------going to school!-------------------------------

Or not so!!!!

6

Mutual Exclusion!

This routine is neither Thread-Safe nor Reentrant!

This piece of code has to be executed Atomically,in Mutual Exclusion! These three linesconstitute a Critical Section.

7

Mutexes

A mutex is a synchronization primitive available for processes and threads. It has two primitives: lock(): allows a thread to acquire the mutex, ensuring that

only one flow of execution exists inside the critical section. If a second thread calls lock(), it becomes blocked.

unlock(): signals that a thread is leaving the critical section. If there are other threads waiting for the critical section, one of them is allowed to run: it becomes ready.

Only one thread/process can be in here!

8

A mutex is also called a BINARY SEMAPHORE!A mutex is also called a BINARY SEMAPHORE!

9

Semaphores – Powerful Friends

A semaphore is a synchronization object Controlled access to a counter (a value) Two operations are supported: wait() and post()

wait() If the semaphore is positive, decrement it and continue If not, block the calling thread (process)

post() Increment the semaphore value If there was any (process) thread blocked due to the

semaphore, unblock one of them.

Semaphores are used to count things! Blocked Processes in a semaphore do

not consume resources (CPU)!

Semaphores are used to count things! Blocked Processes in a semaphore do

not consume resources (CPU)!

10

The anatomy of a Semaphore

5

P1

value

P6 P3 NULLblocked process

list

“A semaphore”

11

“Real Semaphores”

System V Semaphores Works with semaphore arrays semget(), semctl(), semop() A little bit hard to use by themselves

Use a library to encapsulate them!

POSIX Semaphores Quite easy to use sem_init(), sem_close(), sem_post(), sem_wait() Also work with threads! But... in Linux, they only work with threads (kernel 2.4)

Java Typically uses “monitors”, now it has: java.util.concurrent.Semaphore

.NET Typically uses “monitors”, but it has: System.Threading.Semaphore

12

My Semaphore Library

13

Classical – Producer/Consumer Problem

A producer puts elements on a finite buffer. If the buffer is full, it blocks until there’s space.

The consumer retrieves elements. If the buffer is empty, it blocks until something comes along.

We will need three semaphores One to count the empty slots One to count the full slots One to provide for mutual exclusion to the shared buffer

Producer Consumer

14

Producer/Consumer – Basic Implementation

Producer Consumer

empty full

read_pos write_pos

put_element(e) { sem_wait(empty); sem_wait(mutex); buf[write_pos] = e; write_pos = (write_pos+1) % N; sem_post(mutex); sem_post(full);}

mutex

get_element() { sem_wait(full); sem_wait(mutex); e = buf[read_pos]; read_pos = (read_pos+1) % N; sem_post(mutex); sem_post(empty); return e;}

15

The result of executing it!

16

Remember

Always cleanup...

17

Main loop

void producer() { for (int i=TOTAL_VALUES; i>0; i--) { printf("[PRODUCER] Writing %d\n", i); put_element(i); }}

void consumer() { for (int i=0; i<TOTAL_VALUES; i++) { int e = get_element(); printf("[CONSUMER] Retrieved %d\n", e); sleep(1); } terminate();}

void main(int argc, char* argv[]) { init();

if (fork() == 0) { producer(); exit(0); } else { consumer(); exit(0); }}

18

put_element() and get_element()

void put_element(int e) { sem_wait(sem, EMPTY); sem_wait(sem, MUTEX);

buf[write_pos] = e; write_pos = (write_pos+1) % N;

sem_post(sem, MUTEX); sem_post(sem, FULL);}

int get_element() { sem_wait(sem, FULL); sem_wait(sem, MUTEX);

int e = buf[read_pos]; read_pos = (read_pos+1) % N;

sem_post(sem, MUTEX); sem_post(sem, EMPTY);

return e;}

19

init() and terminate()

int sem, shmid;int write_pos, read_pos;int* buf;

void init() { sem = sem_get(3, 0); sem_setvalue(sem, EMPTY, N); // N is the number of slots sem_setvalue(sem, FULL, 0); sem_setvalue(sem, MUTEX, 1);

write_pos = read_pos = 0;

shmid = shmget(IPC_PRIVATE, N*sizeof(int), IPC_CREAT|0700); buf = (int*) shmat(shmid, NULL, 0);}

void terminate() { sem_close(sem); shmctl(shmid, IPC_RMID, NULL);}

20

What should I care?

When you read some data from a pipe…

When you write some data to the network…

When your windows application is processing all those “Open Window”, “Close Window” messages…

When your bank is processing the money you are depositing…

When the a space probe sends images and telemetry from space…

… When you are playing Unreal and all those guys simply “must die”!

21

Classical – Readers/Writers Problem

Writer processes have to update shared data. Reader processes have to check the values of the

data. The should all be able to read at the same time.

Writer

Reader

Shared Data

Writer

Reader

Reader

Why is this different from the Producer/Consumer problem? Why not use a simple mutex?

Why is this different from the Producer/Consumer problem? Why not use a simple mutex?

22

Readers/Writers Problem

We will need two semaphores: One to stop the writers and guarantying mutual exclusion

when a writer is updating the data One to protect mutual exclusion of a shared variable that

counts readers

mutex

n_readers

stop_writers

Writer

Writer

Reader

Reader

Reader

23

Readers/Writers Algorithm (Priority to Readers)

mutex

n_readers

stop_writers

Writer

Writer

Reader Reader

write(e) { sem_wait(stop_writers); buffer = e; sem_post(stop_writers);}

read() { sem_wait(mutex); ++n_readers; if (n_readers == 1) sem_wait(stop_writers); sem_post(mutex); e = buffer;

sem_wait(mutex); --n_readers; if (n_readers == 0) sem_post(stop_writers); sem_post(mutex); return e;}

24

Some Considerations

The previous algorithm gives priority to readers Not always what you want to do

There’s a different version that gives priority to writers

Why should I care? This algorithm is the fundament of all database systems!

Concurrent reads of data; single update.

… One bank agency deposits some money in an account; at the same time, all over the country, many agencies can be reading it

… You are booking a flight. Although someone in England in also booking a flight, you and thousands of people can still see what are the available places in the place.

25

Classical – Buffer Cleaner Problem

A buffer can hold a maximum of N elements. When it is full, it should be immediately emptied. While the buffer is being emptied, no thread can put things into it.

Producer

CleanerProducer

Producer

26

Bounded Buffer Problem

Can you solve it??

27

Classical – Sleeping Barber

A barbershop has N waiting chairs and a barber chair

If there are no customers, the barber goes to sleep

When a customer enters: If all chairs are occupied, he

leaves If the barber is busy but

there are chairs, then the customer sits and waits

If the barber is sleeping, the customer wakes him up

Can you solve it??

28

Synchronization – Some basic rules...

Never Interlock waits! Locks should always be taken in the same order in

all processes Locks should be released in the reverse order they

have been taken

sem_wait(A)sem_wait(B)

// Critical Section

sem_post(B)sem_post(A)

sem_wait(B)sem_wait(A)

// Critical Section

sem_post(A)sem_post(B)

Deadlock!

One way to assure that you always take locks in the same order is to create a lock hierarchy. I.e. associate a number to each lock using a table and always lock in increasing order using that table as reference (index).

29

Synchronization – Some basic rules... (2)

Sometimes it is not possible to know what order to take when locking (or using semaphores) Example: you are using

two resources owned by the operating system. They are controlled by locks. You cannot be sure if another application is not using exactly the same resources and locking in reverse order.

In that case, use pthread_mutex_trylock() or sem_trywait() and back off if you are unsuccessful. Allow the system to make

progress and not deadlock!

30

Synchronization – Some basic rules... (3)

Mutexes are used for implementing mutual exclusion, not for signaling across threads!!! Only the thread that has locked a mutex can unlock it. Not doing

so will probably result in a core dump! To signal across threads use semaphores!

lock(&m)

unlock(&m)

lock(&m)

unlock(&m)

CORRECT!

lock(&m)

unlock(&m)

A B A B

(in mutual exclusion)

(in mutual exclusion)

(blocked)

INCORRECT!

31

Synchronization – Some basic rules... (4)

Don’t mess with thread priorities unless you really need to. Although apparently “correct” and “logical” in design, it’s quite

easy to make a system deadlock, live-lock or some threads to starve.

The Mars PathFinderwould run ok for a while and,after some time, it would stopand reset.

The watch-dog timer wasresetting the system because of some unknown reason…

32

The Mars PathFinder Problem

Priority Inversion Problem (in Mars Path Finder): Low priority thread locks a

semaphore. High priority thread starts to

execute and tries to lock the same semaphore. It’s suspended since it cannot violate the other thread’s lock.

Medium priority threads comes to execute and preempts the low priority thread. Since it doesn’t need the semaphore, it continues to execute.

Meanwhile the high priority thread is starving. After a while, the watchdog timer detects that the high priority thread is not executing and resets the machine.

A

B

C

starts toexecuteand getsthe lock

starts toexecute

tries to get the lock and is suspended

continuesto execute

is preempted asmedium prioritygets to execute

watchdog timer resets the machine as the high priorityone doesn’t get toexecute

(Solution: Thread Locking Priority Inheritance.)

33

Some important concepts to always remember!

DeadlockWhen two or more processes are unable to make progress being blocked waiting for each other

LivelockWhen two or more processes are alive and working but are unable to make progress

StarvationWhen a process is not being able to access resources that its needs to make progress

34

The inventor of the “Semaphore”

Edsger Dijkstra was one of the most brilliant computer scientists ever!

Inventor of the semaphore, one of the key contributions for modern operating systems; developed the THE operating system Used in all operating systems

today!

Created the Dijkstra algorithm for finding the shortest path in a graph Used in all computer networks

today (e.g. in OSPF routing)!

Wrote “A Case against the GO TO Statement”, and was one of the fathers of Algol-60 Which introduced the revolution of

structured programming!

Edsger Dijkstra

35

Writings by E.W. Dijkstra (EWD)

36

Reference

Chapter 6: Process Synchronization Read Section 3.4.1 first 6.1, 6.2, 6.5, 6.6

OperatingSystems

Paulo MarquesDepartamento de Eng. InformáticaUniversidade de [email protected]

2006

/200

7

4. Synchronization4.2. Other Synchronization Primitives

38

Semaphores are correct and very convenient but…Semaphores are correct and very convenient but…

EASY TO GET WRONG!EASY TO GET WRONG!

It’s easy to forget a wait() or post()… It’s easy to do wait() and post() in different semaphores

on opposite order It’s difficult to ensure correctness when several

semaphores are involved

39

Monitors

A monitor is an abstraction where only one thread or process can be executing at a time. Normally, it has associated data When inside a monitor, a thread executes in mutual

exclusion

Thread A

Monitor

Protected Data

Thread B

Thread C

Thread DWant to enterthe monitor…

In mutual exclusion

40

A Monitor in Java

Protected Data

Only one threadcan be inside thesemethods at a time;the others wait outside

41

Monitors (2)

Until now monitors look a lot like a simple MUTEX… Two more primitives are provided:

wait() Suspends the execution of the current thread, immediately relinquishing the monitor. The thread is put on a blocked threads list, waiting to be notified that “something” has changed.

notify() Informs one of the threads that is waiting for something to change that “something” has changed. Thus, one of the awaiting threads is put on the “ready to execute” list. Note that only after the thread that has called notify exits the monitor will the thread that was awaken be allowed to check was has changed and enter the monitor![notifyAll() Notifies all waiting threads to check the condition]

Important! wait() and notify() are not like wait() and post() in

semaphores. If notify is called when there is no awaiting thread, it is lost. There is no associated counter, only the means to notify threads that things changed.

42

putValue() and getValue()

43

Main Program

44

Producer

45

Consumer

46

Running…

47

(A possible) Structure of a monitor

MONITOROnly one thread canbe inside

Blocked threads waiting (ready) to enter the monitor

Blocked threads due to a wait()

Whenever a thread calls wait(),the monitor is released and the

thread is put on the “wait() queue” Whenever a thread calls notify(),one of the threads in the “wait()queue” is transferred to the “ready toenter the monitor” queue

48

Important

Why notifyAll() and not notify()?

Why ‘while’ and not ‘if’?

49

Monitors in Unix?

In “C” you don’t have monitors but you haveCONDITION VARIABLES

Condition variables are somewhat similar to monitors. They allow the programmer to suspend a thread until a certain condition is satisfied or notify a thread that a certain condition has changed. The condition can be anything you like!

Counting semaphores are a special type of condition variables The condition is “counter==0”

50

Primitives

51

Synchronization – Condition Variables (2)

Important rule: A condition variable always has an associated mutex. Always check the condition variable in mutual exclusion. The

mutex must be locked.

How does it work?

Makes thread Acheck its conditionagain

52

Synchronization – Condition Variables (3)

The thread tests a condition in mutual exclusion. If the condition is false, pthread_cond_wait() atomically releases de mutex AND waits until someone signals that the condition should be tested again.

When the condition is signaled AND the mutex is available, pthread_cond_wait() atomically reacquires the mutex AND releases the thread.

pthread_cond_signal() indicates that exactly one blocked thread should test the condition again. Note that this is note a semaphore. If there is no thread blocked, the “signal” is lost.

If all threads should re-check the condition, use pthread_cond_broadcast(). Since a mutex is involved, each one will test it one at a time, in mutual exclusion.

53

Buffer Cleaner Revised

Suppose a buffer that can hold a maximum of N elements. When it is full, it should immediately be emptied. While the buffer is being emptied, no thread can put things into it.

Producer

CleanerProducer

Producer

54

bounded_buffer.c

55

bounded_buffer.c (2)

56

With 3 producers and one cleaner...

57

Condition Variables – Rules (!)

The condition must always be tested with a while loop, never an if! Being unlocked out of a condition variable only means that the condition must be re-checked, not that it has become true

The condition must always be checked and signaled inside a locked mutex.

While condition() may betrue while Thread B is executing,something may happen betweenthe time that the condition is signaledand Thread A is unblock (e.g. anotherthread may change the condition)

58

Classical Problem – Dinning Philosophers

Five philosophers are sitting at a table eating rice from a bowl. For eating rice two chopsticks are needed. There’s only one chopstick on each side of a philosopher.

How to design an algorithm that allows the philosophers to eat without deadlocks, starvation, or live-locking?

59

Dinning Philosophers – Some notes

The following algorithm is bound to deadlock. Why?

The following algorithm may live-lock. Why? (Tip: what happens if all philosophers start to eat at exactly the same time?)

philosopher(i) { wait(chopstick[i]); wait(chopstick[(i+1)%N]); eat(); post(chopstick[(i+1)%N]); post(chopstick[i]);}

philosopher(i) { wait(chopstick[i]); wait(chopstick[(i+1)%N]); eat(); post(chopstick[(i+1)%N]); post(chopstick[i]);}

Try to obtain left chopstick. If successful, obtain right chopstick and eat. If unsuccessful, put down the left chopstick and wait for a while

Try to obtain left chopstick. If successful, obtain right chopstick and eat. If unsuccessful, put down the left chopstick and wait for a while

60

Reference

[Silberschatz05] Chapter 6: Process Synchronization 6.6, 6.7

[Robbins03] Chapter 12: POSIX Threads Chapter 13: Thread Synchronization Chapter 14: Critical Sections and

Semaphores

OperatingSystems

Paulo MarquesDepartamento de Eng. InformáticaUniversidade de [email protected]

2006

/200

7

4. Synchronization4.3. Hardware Issues

62

Bounded-Buffer, from the beginning…

63

Bounded-Buffer, from the beginning…Besides putting the CPU to 100% withoutdoing anything useful, why is it wrong???Besides putting the CPU to 100% withoutdoing anything useful, why is it wrong???

64

Some possible problems with ++nElements;

The compiler may generate the following code:

LD R1, @nElementsADD R1, R1, 1SW @nElements, R1

Depending on the interleaving of putItem() and getItem() the final value can be -1, correct, or +1!!!

65

Some possible problems with ++nElements;

Possible solution… Disable the interrupts!

Works on simple processors with non-preemptive kernels.

But, in a (e.g.) dual-core machine, disabling interrupts in one processor does not prevent the other processor from modifying the variable! Non-preemptive kernels: A process executing in kernel

mode cannot be suspended. E.g. Windows XP, Traditional UNIX

Preemptive Kernels: While executing in kernel mode a process can be suspended. E.g. Linux 2.6.XX series and Solaris 10.

CLILD R1, @nElementsADD R1, R1, #1SW @nElements, R1STI

66

Some possible problems with ++nElements;

Even in architectures providing an atomic INC variable:

it may not work!

INC @nElements

The compiler may optimize a tight loop putting the variable in a register. In another process, the variable may be being written to memory, but it’s not visible from the first process. This is especially relevant in multiprocessor machines.

…while (nElements == 0) ;…

…++nElements;…

Process A Process B

a “smart compiler” has put nElements in a register

nElements is being updatedin memory (or a different register)

67

All leading to…

The need for having atomic locks! The need for having atomic locks!

68

Conditions for solving the critical section problem

Mutual Exclusion Only one process can be

executing in the critical section at a time

Progress If on process is in the

critical section only the processes that are either at the entry or exit sections can be involved in choosing the next process to enter. The decision must be reached in bounded time.

Bounded Waiting No process will starve

indefinitely while trying to enter a critical section in detriment of other processes which repeatedly enter it.

69

Solutions for the Critical Section Problem

Software Algorithms: E.g. Peterson’s Algorithm See Section 6.3 of the book (important!) Complicated to implement and prove correct Very difficult to generalize for several concurrent processes May not work with current hardware

(multi-processor environments with deep speculative out-of-order execution pipelines, having several levels of caches)

In reality, software algorithms implement locks! enter section lock() exit section unlock()

Solutions in hardware Simple “old” single processor machines Disable Interrupts

Even in modern single processor machines may not work TestAndSet and Swap instructions

70

TestAndSet

Instruction that atomically, implemented in hardware, returns the value of a target variable, setting it to TRUE.

Solution for mutual exclusion:

71

Swap

Instruction that atomically swaps the contents of two variables.

Solution for mutual exclusion:

72

Notes on Hardware-Based Support

The two previous algorithms: Solve the mutual exclusion problem and the progress

condition Do not satisfy the bounded waiting condition (why??) Slightly more sophisticated algorithms exist that do so

The implementations shown before are called spin-locks In general they should be avoided Nevertheless, in controlled ways, they are the basis for

implementing true mutexes and semaphores at the operating system level

This is especially relevant on preemptive kernels running on multiprocessors. (Somehow, the kernel must, in a controlled way, allow to signal across processors in mutual exclusion!)

In some high performance applications, sometimes programmers spin-lock for a few moments before blocking on a true lock/semaphore. In this way they are able to prevent a heavy process switch if the resource becomes available in a very short time.

73

Linux 2.6.XX

Linux 2.6 is a fully preemptive kernel. I.e. processes can be suspended while executing in kernel mode.

Basic underlying locking mechanism depends on whether an SMP or non-SMP kernel is being used.

Spin-locks are only used for very short durations. Longer durations imply passing control to a true

lock/semaphore, releasing the processor(s) involved.

single processor multi-processor

Disable kernel preemption Acquire spin-lock

Enable kernel preemption Release spin-lock

74

Reference

Chapter 6: Process Synchronization Read Section 3.4.1 first 6.1, 6.2, 6.3, 6.4, 6.8