Chapter 6 - Synchronization Tools - Part 2

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 32

Chapter 6

Synchronization
Tools
Edited by
Ghada Mohammed S AlOsaimi
CS Lecturer, CCIS, IMISIU
Outline
 Mutex Locks
 Semaphores
 Liveness

Process
Management

Protection and Memory


Security Introduction Management
and Overview

Storage
Management

2
Objectives
 Demonstrate how mutex locks, semaphores, and condition variables

can be used to solve the critical section problem

 Evaluate tools that solve the critical-section problem in low-. Moderate-, and high-contention scenarios

3
Mutex Locks

 Previous solutions (HW-based) are complicated and generally inaccessible to application programmers
 OS designers build software tools to solve critical section problem
 Simplest one is mutex lock, a higher-level SW-based tool
 Protect a critical section by first acquire() a lock then release() the lock
 Boolean variable indicating if lock is available or not, with initial value true
 Calls to acquire() and release() must be atomic
 Usually implemented via hardware atomic instructions such as compare-and-swap

4
Mutex Locks (cont.)

 Usually, used to synchronize threads


 Acts like a "lock" protecting access to a shared data resource
 Basic concept of a mutex as used in Pthreads is that only one thread can lock (or own) a mutex variable at any given time
 If several threads try to lock a mutex, only one thread will be successful

 No other thread can own that mutex until the owning thread unlocks that mutex
 Threads must "take turns" accessing protected data
 Very often example; the action performed by a thread owning a mutex is the updating of global variables
 Safe way to ensure that when several threads update the same variable, the final value is the same as what it would be
if only one thread performed the update

 Variables being updated belong to a "critical section"

5
Mutex Locks (cont.)
Thread 1 Thread 2
 A typical sequence in the use of a mutex is as follows
Acquire mutex lock Acquire mutex lock
 Create and initialize a mutex variable

 Several threads attempt to lock the mutex Critical section


 Only one succeeds and that thread owns the mutex
Unlock/Release mutex
 The owner thread performs some set of actions

 The owner unlocks the mutex


Critical section
 Another thread acquires the mutex and repeats the process
Unlock/Release mutex
 Finally, the mutex is destroyed

6
Solution to Critical-section Problem Using Locks

while (true) {  acquire() {


while (!available)

acquire lock ; /* busy wait */


available = false;;
critical section }

release lock  release() {


available = true;
remainder section
} }

7
Mutex Locks (cont.)

 Main disadvantage of this mutex lock implementation, it requires busy waiting


 While a process is in its critical section, any other process that tries to enter its critical section must loop
continuously in the call to acquire()until first process finish
 Wastes CPU cycles that some other process might be able to use productively

 This lock therefore called a spinlock


 Because a process “spins” while waiting for the lock to become available
 Spinlocks do have an advantage, that no context switch is required when a process must wait on a lock, and a
context switch may take considerable time

8
Mutex Locks in Pthreads (POSIX Threads)

 Creating / Destroying Mutexes


 pthread_mutex_init ( pthread_mutex_t mutex, pthread_mutexattr_t attr)

 pthread_mutex_destroy ( pthread_mutex_t mutex )

 pthread_mutexattr_init ( pthread_mutexattr_t attr )

 pthread_mutexattr_destroy ( pthread_mutexattr_t attr )

 Locking / Unlocking Mutexes


 pthread_mutex_lock ( pthread_mutex_t mutex )

 pthread_mutex_trylock ( pthread_mutex_t mutex )

 pthread_mutex_unlock ( pthread_mutex_t mutex )


9
Creating / Destroying Mutexes

 Creates and initializes a new mutex object, then sets its attributes according to the mutex attributes object (attr)

 Mutex is initially unlocked

 Mutex variables must be of type pthread_mutex_t

 attr object is used to establish properties for the mutex object

 It must be of type pthread_mutexattr_t

 May be specified as NULL to accept defaults

 pthread_mutexattr_init( )and pthread_mutexattr_destroy( ) routines are used to create and


destroy mutex attribute objects respectively

 Both return 0 on success

 pthread_mutex_destroy( ) should be used to free a mutex object which is no longer needed


10
Locking / Unlocking Mutexes

pthread_mutex_lock( )
 Used by a thread to acquire a lock on the specified mutex variable
 If the mutex is already locked by another thread, the call will block the calling thread until the mutex is unlocked

pthread_mutex_trylock( )
 Attempt to lock a mutex
 If the mutex is already locked, the routine will return immediately
 This routine may be useful in preventing deadlock conditions, as in a priority-inversion situation

pthread_mutex_unlock( )
 Unlock a mutex if called by the owning thread
 Calling this routine is required after a thread has completed its use of protected data if other threads are to acquire the
mutex for their work with the protected data
11
Example – No Mutex Locks

12
Example – With Mutex Locks

13
Another Example

14
Semaphore

 Previous solutions are complicated and generally inaccessible to application programmers


 OS designers build software tools to solve critical section problem
 Synchronization tool that provides more sophisticated ways (than Mutex locks) for process to synchronize their activities.
 BUT it robust !
 Semaphore S – integer variable
 Can only be accessed via two indivisible (atomic) operations wait() and signal()(Originally called P() and V())
 Definition of the wait() operation
wait(S) {
while (S <= 0); // busy wait
S--;
}
 Definition of the signal() operation
signal(S) {
15 S++;
}
Semaphore Usage

Counting semaphore – integer value can range over an unrestricted domain


Binary semaphore – integer value can range only between 0 and 1 ( same as a mutex lock )
 Can solve various synchronization problems
 Consider P1 and P2 that require S1 to happen before S2
Create a semaphore “synch” initialized to 0 or N ( depend on semaphore type)
P1:
S1;
signal(synch);
P2:
wait(synch);
S2;

16
Counting Semaphore

 This type of semaphore is used in managing system resources


 Counters for resources shared between processes/threads
 That’s mean it is used to control access control access to a given resource consisting of a finite number of instances
 Here semaphore S is initialized to the number of resources available
 Each process that wishes to use a resource performs a wait() operation on the semaphore (thereby decrementing S )
 When a process releases a resource, it performs a signal() operation (incrementing S )
 When the S for the semaphore goes to 0, all resources are being used.
 After that, processes that wish to use a resource will block until the count becomes greater than 0
 Can implement a counting semaphore S as a binary semaphore

17
Semaphore Implementation

 Must guarantee that no two processes can execute 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
 Could now have busy waiting as mutex lock critical section implementation
 But implementation code is short
 Little busy waiting if critical section rarely occupied
 Note that applications may spend lots of time in critical sections and therefore this is not a good solution
 Better Solution !
 Implement semaphore with no busy waiting

18
Semaphore Implementation with no Busy waiting

 To overcome the need for busy waiting, we can modify the definition of the wait() and signal() semaphore operations
 When a process executes wait() operation and finds that semaphore value is not positive, it must wait

 However, rather than engaging in busy waiting, the process can block() itself

 The block operation places a process into a waiting queue associated with the semaphore, and the state of the
process is switched to the waiting state ; with each semaphore there is an associated waiting queue

 Then control is transferred to the CPU scheduler, which selects another process to execute

 The process that is suspended, waiting on a semaphore S, should be restarted when some other process executes a
signal() operation

 Restarting process is done by a wakeup(), which changes the process to the ready state

 The process is then placed in the ready queue


19
Implementation with no Busy waiting (Cont.)

 Each entry in a waiting queue has two data items


 value (of type integer)
 pointer to next record in the list of processes (list of PCBs)
 Two operations
 block – place the process invoking the operation on the appropriate waiting queue
 wakeup – remove one of processes in the waiting queue and place it in the ready queue
 So, semaphore definition will be as
typedef struct {
int value;
struct process *list;
} semaphore;

20
Implementation with no Busy waiting (Cont.)

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);
}
}
21
POSIX Semaphore Functions

 Initializing semaphores
 Decreasing the count of a semaphore
 Increasing the count of a semaphore
 Destroying semaphores

22
POSIX Semaphore Functions (cont.)

 All of the POSIX semaphore functions return -1 to indicate an error and 0 on success
 sem_init function
 initializes the semaphore sem to have the value value
 value parameter cannot be negative
 pshared value:
 0: only threads of the creating process can use the semaphore
 Non-0: other processes can use the semaphore (i.e. the process that initializes it and by children of that process)
 sem_wait
 Standard semaphore wait operation
 If the semaphore sem is 0, the sem_wait blocks unit it can successfully decrement the semaphore value

23
POSIX Semaphore Functions (cont.)

 sem_trywait
 It is similar to sem_wait except that instead of blocking when attempting to decrement a zero-valued semaphore, it
returns -1
 Test a semaphore’s current condition
 sem > 0: thread acquires lock ; sem == 0: thread returns

 sem_post
 Standard semaphore signal operation
 sem > 0: no threads were blocked on this semaphore, the semaphore value is incremented
 sem == 0: one blocked thread will be allowed to run

24
Basic Synchronization Patterns

1 | Signaling
 Represents the simplest use for a semaphore which is signaling
 One process/thread sends a signal to another process/thread to
indicate that something has happened
 Pseudo-code describes the solutions

25
Basic Synchronization Patterns (cont.)

2 | Rendezvous
 Generalize the signal pattern so that it works both ways; Process One has to wait for Process Two and vice versa
 Pseudo-code describes the problem:

 To guarantee that a1 happens before b2 and b1 happens before a2; need to use two semaphores
 Pseudo-code describes the tried solution:

26  Deadlock: While working on the previous problem, you might have tried something like this
Basic Synchronization Patterns (cont.)

2 | Rendezvous
 Pseudo-code describes the solution 1:

 Pseudo-code describes the solution 2:

27
Basic Synchronization Patterns (cont.)

3 | Mutual exclusion
 Use a semaphore to guarantee that only one process/thread accesses a shared memory (or global variables at a time
 Pseudo-code describes the solution:

28
POSIX Semaphore – Critical Section Problem

29
Problems with Semaphores

 Incorrect use of semaphore can result in timing errors that are difficult to detect, since these errors happen only if particular
execution sequences take place, and these sequences do not always occur
 Examples: suppose that a program replaces signal(mutex) with wait(mutex)

 These – and others – are examples of what can occur when semaphores and other synchronization tools are used incorrectly

30
Liveness

 Processes may have to wait indefinitely while trying to acquire a synchronization tool such as a mutex lock or semaphore
 Waiting indefinitely violates the progress and bounded-waiting criteria discussed at the beginning of this chapter
 Liveness refers to a set of properties that a system must satisfy to ensure processes make progress
 Indefinite waiting (busy wait loop) and deadlock are examples of a liveness failure
 Deadlock – two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting
processes
P P1
 Let S and Q be two semaphores initialized to 1 wait(S); wait(Q);
 Consider if P0 executes wait(S) and P1 wait(Q) wait(Q); wait(S);
... ...
 When P0 executes wait(Q), it must wait until P1 executes signal(Q) signal(S); signal(Q);
signal(Q); signal(S);
 However, P1 is waiting until P0 execute signal(S)
 Since these signal() operations will never be executed, P0 and P1 are deadlocked

31
Liveness (cont.)

 Other forms of deadlock:


 Starvation – indefinite blocking
 A process may never be removed from the semaphore queue in which it is suspended
 Priority Inversion – Scheduling problem when lower-priority process holds a lock needed by higher-priority process
 Consider the scenario with three processes P1, P2, and P3. P1 has the highest priority, P2 the next highest, and P3 the
lowest. Assume a resource P3 is assigned a resource R that P1 wants. Thus, P1 must wait for P3 to finish using the resource.
However, P2 becomes runnable and preempts P3. What has happened is that P2 - a process with a lower priority than P1 - has
indirectly prevented P3 from gaining access to the resource.

 To prevent this from occurring, a priority inheritance protocol is used. This simply allows the priority of the highest thread
waiting to access a shared resource to be assigned to the thread currently using the resource. Thus, the current owner of the
resource is assigned the priority of the highest priority thread wishing to acquire the resource.

32

You might also like