OS-Lab9-manual 2
OS-Lab9-manual 2
LAB
MANUAL
Course: CSC322-Operating Systems
Learning Procedure
1) Stage J (Journey inside-out the concept)
2) Stage a1 (Apply the learned)
3) Stage v (Verify the accuracy)
4) Stage a2 (Assess your work)
Lab # 04
Lab # 05
Lab # 08
Lab # 10
Lab # 11
Lab # 13
Lab # 14
Lab # 15
Lab # 16
Terminal Examination
2
LAB # 09
Statement Purpose:
This lab will give you’ll implement Process synchronization (i.e. Mutex and
Semaphores) in C++ language using threads.
Activity Outcomes:
The primary objective of this lab is to implement the process synchronization by:
Creating/Destroying Mutexes
Locking/Unlocking Mutexes
Using Semaphores
Instructor Note:
1) Stage J (Journey)
Introduction
When multiple threads are running they will invariably need to communicate with each other in order
synchronize their execution. One main benefit of using threads is the ease of using synchronization
facilities.
Threads need to synchronize their activities to effectively interact. This includes:
Implicit communication through the modification of shared data
Explicit communication by informing each other of events that have occurred.
This lab describes the synchronization types available with threads and discusses when and how to use
synchronization. There are a few possible methods of synchronizing threads and here we will discuss:
1. Mutual Exclusion (Mutex) Locks
2. Semaphores
If threads execute this code independently it will lead to garbage. The access to the common_variable
by both of them simultaneously is prevented by having a lock, performing the thing and then releasing
the lock.
1. Mutexes
Mutual exclusion locks (mutexes) can prevent data inconsistencies due to race conditions. A race
condition often occurs when two or more threads need to perform operations on the same memory area,
but the results of computations depends on the order in which these operations are performed.
3
Mutex Variables:
Mutex is a shortened form of the words "mutual exclusion". Mutex variables are one of the primary
means of implementing thread synchronization. A mutex variable acts like a "lock" protecting access
to a shared data resource. The 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. Thus, even 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 the action performed by a thread owning a mutex is the updating of global variables. This
is a 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. The variables being updated belong to a
"critical section".
A typical sequence in the use of a mutex is as follows:
When several threads compete for a mutex, the losers block at that call an unblocking call is available
with "trylock" instead of the "lock" call.
Mutex variables must be of type pthread_mutex_t. The attr object is used to establish properties for the
mutex object, and must be of type pthread_mutexattr_t if used (may be specified as NULL to accept
defaults). If implemented, the pthread_mutexattr_init( ) and pthread_mutexattr_destroy( ) routines are
used to create and destroy mutex attribute objects respectively.
pthread_mutex_destroy( ) should be used to free a mutex object which is no longer needed.
4
pthread_mutex_lock( ) routine is 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( ) will attempt to lock a mutex. However, 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.
Mutex contention: when more than one thread is waiting for a locked mutex, which thread will be
granted the lock first after it is released? Unless thread priority scheduling (not covered) is used,
the assignment will be left to the native system scheduler and may appear to be more or less random.
pthread_mutex_unlock( ) will 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.
An error will be returned:
If the mutex was already unlocked
If the mutex is owned by another thread.
2. Semaphores
Semaphore is another tool to synchronize activities in a computer. The concept of semaphores as
used in computer synchronization is due to the Dutch computer scientist Edsgar Dijkstra. They
have the advantages of being very simple, but sufficient to construct just about any other
synchronization function you would care to have.
There are several versions to implement semaphores. The most common versions are:
1) POSIX semaphores
2) System V IPC semaphores
This lab will consider only POSIX semaphore, since POSIX semaphores has very clear API
functions to perform semaphore operations. However, it is more efficient to use System V
semaphore than POSIX semaphore when semaphores are shared between processes.
What is a Semaphore?
A semaphore is an integer variable with two atomic operations:
1) wait. Other names for wait are P, down and lock.
2) signal: Other names for signal are V, up, unlock and post.
The simplest way to understand semaphore is, of course, with code. Here is a little pseudo-code
that may help.
5
S->value++;
if (S->value <= 0)
unblock one process or thread that is blocked on semaphore
}
Example 1 (Signaling)
This example represents the simplest use for a semaphore (which is signaling), in which one
process/thread sends a signal to another process/thread to indicate that something has happened.
Suppose process one must execute statement a before process two executes statement b. To solve this
synchronization problem, we need a semaphore (say sync) with initial value 0, and that both processes
have shared access to it. The following pseudo-code describes the solutions:
Example 2 (Rendezvous)
Puzzle: Generalize the signal pattern so that it works both ways. Process One has to wait for Process
Two and vice versa. In other words, given this code.
we want to guarantee that a1 happens before b2 and b1 happens before a2. Please try to think how
to solve this puzzle before proceeding to next paragraph.
Hint: You need to use two semaphores.
While working on the previous problem, you might have tried something like this :
6
if so, I hope you will reject it quickly because it has serious problem of Deadlock! Solutions for this
problem are:
Solution 1:
Solution 2:
This solution is probably less efficient, since it might have to switch between process One and Two
more than necessary.
POSIX Semaphore
Semaphores are part of the POSIX.1b standard adopted in 1993. The POSIX.1b standard defines two
types of semaphores: named and unnamed. A POSIX.1b unnamed semaphore can be used by a single
process or by children of the process that created them. A POSIX.1b named semaphore can be used by
any processes. In this section, we will consider only how to initialize unnamed semaphore.
7
The following header summarizes how we can use POSIX.1b unnamed semaphore:
Lab Activities
Activity 1.a:
Write a program in C++ to use Mutexes.
This simple example code demonstrates the use of several Pthread mutex routines. The serial version
may be reviewed first to compare how the threaded version performs the same task.
#include <stdio.h>
#include <pthread.h>
/* The function run when the thread is created */
void* compute_thread (void*);
main( )
{
/* This is data describing the thread created */
pthread_t tid; /* thread ID structure */
pthread_attr_t attr; /* thread attributes */
char hello[ ] = {"Hello, "}; /* some typical data */
char thread[ ] = {"thread"};
/* Initialize the thread attribute structure */
pthread_attr_init(&attr);
/* Create another thread. ID is returned in &tid */
/* The last parameter passed to the thread function */
pthread_create(&tid, &attr, compute_thread, thread);
/* Continue on with the base thread */
8
printf(hello);
sleep(1);
printf("\n");
exit(0);
}
/* The thread function to be run */
void* compute_thread(void* dummy) {
printf(dummy); return; }
Hello, thread
After creating a new thread, main process prints “Hello, ” and then sleeps. The new thread then
prints “thread”.
Activity 1.b:
This example shows how to pass multiple arguments via a structure. You can pass any data
type in a thread callback because it points to void as explained in the following example
#include <pthread.h>
#include <stdio.h>
/* Function run when the thread is created */
void* compute_thread (void*);
/* This is the lock for thread synchronization */
pthread_mutex_t my_sync;
main( )
{
/* This is data describing the thread created */
pthread_t tid;
pthread_attr_t attr;
char hello[ ] = {"Hello, "};
char thread[ ] = {"thread"};
/* Initialize the thread attributes */
pthread_attr_init (&attr);
/* Initialize the mutex (default attributes) */
pthread_mutex_init (&my_sync,NULL);
/* Create another thread. ID is returned in &tid */
/* The last parameter is passed to the thread function */
/* Note reversed the order of "hello" and "thread" */
pthread_create(&tid, &attr, compute_thread, hello);
sleep(1); /* Let the thread get started */
/* Lock the mutex when it's our turn to do work */
pthread_mutex_lock(&my_sync);
printf(thread);
printf("\n");
pthread_mutex_unlock(&my_sync);
9
exit(0);
}
/* The thread function to be run */
void* compute_thread(void* dummy)
{
/* Lock the mutex when its our turn */
pthread_mutex_lock(&my_sync);
printf(dummy);
pthread_mutex_unlock(&my_sync);
sleep(1); return;
}
Hello, thread
The main process sleeps after creating new thread which prints “Hello, ”. After waking up,
the main process prints “thread”. Printf is treated as Critical Section and hence guarded
with Mutex Lock so that no two process can print at the same time.
Activity 2:
Semaphores (Signaling):
Lab1.c #include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
/* Global variables */
int x = 0;
sem_t sync;
/* Thread function */
void *my_func(void *arg)
{
/* wait for signal from main thread */
sem_wait(&sync);
printf("X = %d\n", x);
}
void main ()
{
pthread_t thread;
/* semaphore sync should be initialized by 0 */
if (sem_init(&sync, 0, 0) == -1) {
perror("Could not initialize mylock semaphore");
exit(2);
}
if (pthread_create(&thread, NULL, my_func, NULL) < 0) {
perror("Error: thread cannot be created");
exit(1);
}
10
/* perform some operation(s) */
x = 55;
/* send signal to the created thread */
sem_post(&sync);
/* wait for created thread to terminate */
pthread_join(thread, NULL);
/* destroy semaphore sync */
sem_destroy(&sync);
exit(0);
}
The output of this code would be:
Final value of x is 10
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
//using namespace std;
sem_t lock; //lock is defined as semaphore
//routine for thread 1
void *thread1(void *varg)
{
sem_wait(&lock);
printf("this from Thread 1\n"); // CS
sem_post(&lock);
return NULL;
}
//routine for thread 2
void *thread2(void *varg)
{
sem_wait(&lock);
printf("this from Thread 2\n"); //CS
sem_post(&lock);
return NULL;
}
int main()
{
sem_init(&lock,0,1); // lock is initialized as 1 with last argument.
middle argument is for threads(i.e. 1 for processes and 0 for threads)
pthread_t t1, t2;
printf("Before Thread\n");
pthread_create(&t1, NULL, thread1,NULL );
pthread_create(&t2, NULL, thread2,NULL );
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("After Thread\n");
return 0;
11
}
12
13