Anda di halaman 1dari 7

JAYPEE INSTITUTE OF INFORMATION TECHNOLOGY, NOIDA

OPERATING SYSTEMS (10B17CI571)


LAB ASSIGNMENT 6

PROCESS SYNCHRONIZATION (MUTEX & CONDITION VARIABLES)

In this lab exercise we discuss process synchronization using methods of


Mutual Exclusion (Mutex), Semaphores as Condition Variables

PART-I:
Objective
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.

Introduction to Mutexes & Condition Variables: Read Mutexes.pdf in help folder


Example 6.1 Code for threadonly.c */
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
pthread_t tid[2];
int counter;
void* doSomeThing(void *arg){
unsigned long i = 0;
counter += 1;
printf("\n Job %d started\n", counter);
for(i=0; i<(0xFFFFFFFF);i++);
printf("\n Job %d finished\n", counter);
return NULL;}
int main(void)
{
int i = 0;
int err;
while(i < 2)
{
err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
if (err != 0)
printf("\ncan't create thread :[%s]", strerror(err));
i++;
}

pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
return 0;
}

Q1: Compile and run the codes given in examples 6.1 and 6.2. The code in example 6.1
demonstrates the threaded version without mutex and code in example 6.2 uses of several
Pthread mutex routines.

Visualize the execution of the code, youll find that :

The log Job 2 started is printed just after Job 1 Started so it can easily be concluded
that while thread 1 was processing the scheduler scheduled the thread 2.
If the above assumption was true then the value of the counter variable got incremented
again before job 1 got finished.
So, when Job 1 actually got finished, then the wrong value of counter produced the log
Job 2 finished followed by the Job 2 finished for the actual job 2 or vice versa as it is
dependent on scheduler.
So we see that its not the repetitive log but the wrong value of the counter variable that
is the problem.

The actual problem was the usage of the variable counter by second thread when the first
thread was using or about to use it. In other words we can say that lack of synchronization
between the threads while using the shared resource counter caused the problems or in one
word we can say that this problem happened due to Synchronization problem between two
threads.
The most popular way of achieving thread synchronization is by using Mutexes

Example 6.2 /* Code for mutex-thread.c */


#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
pthread_t tid[2];
int counter;
pthread_mutex_t lock;
void* doSomeThing(void *arg)
{
pthread_mutex_lock(&lock);
unsigned long i = 0;
counter += 1;
printf("\n Job %d started\n", counter);
for(i=0; i<(0xFFFFFFFF);i++);
printf("\n Job %d finished\n", counter);
pthread_mutex_unlock(&lock);
return NULL;
}
int main(void)
{
int i = 0;
int err;
if (pthread_mutex_init(&lock, NULL) != 0)
{
printf("\n mutex init failed\n");
return 1;
}
while(i < 2)
{
err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
if (err != 0)
printf("\ncan't create thread :[%s]", strerror(err));
i++;
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&lock);
return 0;
}

A Mutex is a lock that we set before using a shared resource and release after using it. When the
lock is set, no other thread can access the locked region of code. So we see that even if thread 2
is scheduled while thread 1 was not done accessing the shared resource and the code is locked by
thread 1 using mutexes then thread 2 cannot even access that region of code. So this ensures a
synchronized access of shared resources in the code.
Internally it works as follows :

Suppose one thread has locked a region of code using mutex and is executing that piece
of code.
Now if scheduler decides to do a context switch, then all the other threads which are
ready to execute the same region are unblocked.
Only one of all the threads would make it to the execution but if this thread tries to
execute the same region of code that is already locked then it will again go to sleep.
Context switch will take place again and again but no thread would be able to execute the
locked region of code until the mutex lock over it is released.
Mutex lock will only be released by the thread who locked it.
So this ensures that once a thread has locked a piece of code then no other thread can
execute the same region until it is unlocked by the thread who locked it.
Hence, this system ensures synchronization among the threads while working on shared
resources.

Q2: Compile and run the given code in Example 6.3 using mutex for synchronization of critical
section.
Example 6.3 /* Code for mutex-c */
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex; /* mutex id */
main(){
pthread_t idA, idB; /* ids of threads */
void *MyThread(void *);
if (pthread_mutex_init(&mutex, NULL) < 0) {
perror("pthread_mutex_init");
exit(1);
}
if (pthread_create(&idA, NULL, MyThread, (void *)"A") != 0) {
perror("pthread_create");
exit(1);
}
if (pthread_create(&idB, NULL, MyThread, (void *)"B") != 0) {
perror("pthread_create");
exit(1);
}
(void)pthread_join(idA, NULL);
(void)pthread_join(idB, NULL);
(void)pthread_mutex_destroy(&mutex);
}
int x = 0;

/* global shared variable */

void *MyThread(void *arg){


char *sbName;
sbName = (char *)arg;
IncrementX();
printf("X = %d in Thread %s\n", x, sbName);
}
IncrementX()
{
int Temp; /* local variable */
BeginRegion(); /* enter critical region */
Temp = x;
Temp = Temp + 1;
x = Temp;
EndRegion();
/* exit critical region */
}
BeginRegion()
{
pthread_mutex_lock(&mutex);
}
EndRegion()
{
pthread_mutex_unlock(&mutex);
}

Exercise
1. Write a C program using threads and mutexes, whereby functionincr() is used to
increment a shared counter variable and functiondecr() is used to decrement shared
counter variable. Use mutex locks at appropriate places so as to synchronize the two
threads. Initialize counter to 0 in the beginning.
PART-II
SEMAPHORES
Introduction to Semaphores: Read Semaphores.pdf in help folder
A simple example is as follows. Imagine a thread creates another thread and
then wants to wait for it to complete its execution:
----------------------------------------------------------------------------void *
child(void *arg) {
printf("child\n");
// signal here: child is done
return NULL;
}
int
main(int argc, char *argv[]) {
printf("parent: begin\n");
pthread_t c;
Pthread_create(c, NULL, child, NULL);
// wait here for child
printf("parent: end\n");
return 0;
}
----------------------------------------------------------------------------[PARENT WAITING FOR CHILD]
What we would like to see here is the following output:
----------------------------------------------------------------------------parent: begin
child
parent: end
----------------------------------------------------------------------------[OUTPUT FROM PARENT WAITING FOR CHILD]
The question, then, is how to use a semaphore to achieve this effect, and is
it turns out, it is quite simple, as we see here:
----------------------------------------------------------------------------sem_t s;
void *
child(void *arg) {
printf("child\n");
// signal here: child is done
sem_post(&s);

return NULL;
}
int
main(int argc, char *argv[]) {
sem_init(&s, 0, X); // what should X be?
printf("parent: begin\n");
pthread_t c;
Pthread_create(c, NULL, child, NULL);
// wait here for child
sem_wait(&s);
printf("parent: end\n");
return 0;
}
----------------------------------------------------------------------------[PARENT WAITING FOR CHILD WITH A SEMAPHORE]
As you can see in the code, the parent simply calls sem_wait() and the child
sem_post() to wait for the condition of the child finishing its execution to
become true. However, this raises the question: what should the initial value
of this semaphore be? (think about it here, instead of reading ahead)
The answer, of course, is that the value of the semaphore should be set to is
the number 0. There are two cases to consider. First, let us assume that the
parent creates the child but the child has not run yet (i.e., it is sitting
in a ready queue but not running). In this case, the parent will call
sem_wait() before the child has called sem_post(), and thus we'd like the
parent to wait for the child to run. The only way this will happen is if the
value of the semaphore is not greater than 0; hence, 0 as the initial value
makes sense. When the child finally runs, it will call sem_post(),
incrementing the value to 1 and waking the parent, which will then return
from sem_wait() and complete the program.
The second case occurs when the child runs to completi on before the parent
gets a chance to call sem_wait(). In this case, the child will first call
sem_post(), thus incrementing the value of the semaphore from 0 to 1. When
the parent then gets a chance to run, it will call sem_wait() and find the
value of the semaphore to be 1; the parent will thus decrement the value and
return from sem_wait() without waiting, also achieving the desired effect.

Exercise
1. WAP to solve the classic producer consumer bounded buffer problem using
mutexes and semaphores
2. WAP to solve the classic reader-writer problem using mutexes and semaphores

Anda mungkin juga menyukai