0% found this document useful (0 votes)
18 views14 pages

Producer Consumer

The document is a lab manual focused on synchronization techniques in operating systems, specifically semaphores and mutexes. It explains their definitions, operations, and implementations in Linux, along with examples like the producer-consumer and readers-writers problems. The manual includes code snippets demonstrating the application of these synchronization methods in multi-threaded environments.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
18 views14 pages

Producer Consumer

The document is a lab manual focused on synchronization techniques in operating systems, specifically semaphores and mutexes. It explains their definitions, operations, and implementations in Linux, along with examples like the producer-consumer and readers-writers problems. The manual includes code snippets demonstrating the application of these synchronization methods in multi-threaded environments.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 14

Operating System

Lab Manual 10

Topic: Synchronization (Semaphore & Mutex)

Lab Instructor: Muhammad Rizwan

Session: Fall 2023


School of Systems and Technology
UMT Lahore Pakistan

1
Process/Thread Synchronization
(Using Semaphores & Mutex)
Process/Thread Synchronization is the coordination of execution of multiple
processes/threads in a multi-process system/ multithreading system to ensure that they
access shared resources in a controlled and predictable manner. The main objective of
process/thread synchronization is to ensure that multiple processes/thread access shared
resources without interfering with each other, and to prevent the possibility of inconsistent
data due to concurrent access. To achieve this, operating system based synchronization
techniques offers semaphores and mutex.

What is Semaphore?
A semaphore is a signaling mechanism and a thread that is waiting on a semaphore can be
signaled by another thread. This is different than a mutex as the mutex can be signaled only
by the thread that is called the wait function.
A semaphore is fundamentally an integer whose value is never allowed to fall below 0.
There are two atomic operations on a semaphore: wait and post. The post operation
increments the semaphore by 1, and the wait operations does the following: If the
semaphore has a value > 0, the semaphore is decremented by 1. If the semaphore has value
0, the caller will be blocked (busy-waiting or more likely on a queue) until the semaphore
has a value larger than 0, and then it is decremented by 1.
There are two types of semaphores: Binary Semaphores and Counting Semaphores.
 Binary Semaphores: They can only be either 0 or 1. They are also known as mutex
locks, as the locks can provide mutual exclusion. All the processes can share the same
mutex semaphore that is initialized to 1. Then, a process has to wait until the lock
becomes 0. Then, the process can make the mutex semaphore 1 and start its critical
section. When it completes its critical section, it can reset the value of the mutex
semaphore to 0 and some other process can enter its critical section.
 Counting Semaphores: They can have any value and are not restricted over a certain
domain. They can be used to control access to a resource that has a limitation on the
number of simultaneous accesses. The semaphore can be initialized to the number of
instances of the resource. Whenever a process wants to use that resource, it checks if the
number of remaining instances is more than zero, i.e., the process has an instance
available. Then, the process can enter its critical section thereby decreasing the value of
the counting semaphore by 1. After the process is over with the use of the instance of the
resource, it can leave the critical section thereby adding 1 to the number of available
instances of the resource.

2
Semaphore in LINUX
We declare a semaphore as:

sem_t sem;

where sem_t is a typedef defined in a header file. An example of this might be that we have a
set of N interchangeable resources. We start with semaphore S = N. We use a resource, so
there are now N-1 available (wait), and we return it when we are done (post). If the semaphore
has value 0, there are no resources available, and we have to wait (until someone does a post).

Semaphores are thus used to coordinate concurrent processes. This is what some people call a
"counted semaphore". There is a similar notion called a "binary semaphore" which is limited
to the values 0 and 1.

Semaphore Initialization
sem_init()

Prototype: int sem_init(sem_t * sem, int pshared, unsigned int value);

Library: #include <semaphore.h>

Purpose: This initializes the semaphore *sem. The initial value of the semaphore will be
value. If pshared is 0, the semaphore is shared among all threads of a process (and hence need
to be visible to all of them such as a global variable). If pshared is not zero, the semaphore is
shared but should be in shared memory.

Notes:

 On success, the return value is 0, and on failure, the return value is -1.
 An attempt to initialize a semaphore that has already been initialized results in
undefined behavior.

Semaphore Wait Function


sem_wait()

Prototype: int sem_wait(sem_t * sem);

Library: #include <semaphore.h>

3
Purpose: This implements the wait function described above on the semaphore *sem.

Notes:

 Here sem_t is a typdef defined in the header file as (apparently) some variety of
integer.
 On success, the return value is 0, and on failure, the return value is -1 (and the value of
the semaphore is unchanged).
 There are related functions sem_trywait() and sem_timedwait().

Semaphore Post Function


sem_post()

Prototype: int sem_post(sem_t * sem);

Library: #include <semaphore.h>

Purpose: This implements the post function described above on the semaphore *sem.

Note: On success, the return value is 0, and on failure, the return value is -1 (and the value of
the semaphore is unchanged).

Semaphore Destroy Function


sem_destroy()

Prototype: int sem_destroy(sem_t * sem);

Library: #include <semaphore.h>

Purpose: This destroys the semaphore *sem, so *sem becomes uninitialized.

Notes:

 On success, the return value is o, and on failure, the return value is -1.
 Destroying a semaphore on which other processes or threads are waiting (using
sem_wait()) or destroying an uninitialized semaphore will produce undefined results.

4
What is a mutex in LINUX?
A mutex (named for "mutual exclusion") is a binary semaphore with an ownership restriction:
it can be unlocked (the post operation) only by whoever locked it (the wait operation). Thus a
mutex offers a somewhat stronger protection than an ordinary semaphore.

We declare a mutex as:

pthread_mutex_t mutex;
Mutex Initialization
pthread_mutex_init()

Prototype:

int pthread_mutex_init(pthread_mutex_t * restrict mutex,


const pthread_mutexattr_t * restrict attr);

Library: #include <pthread.h>

Purpose: This initializes *mutex with the attributes specified by attr. If attr is NULL, a default
set of attributes is used. The initial state of *mutex will be "initialized and unlocked".

Notes:

 If we attempt to initialize a mutex already initialized, the result is undefined.


 On success, the return value is 0, and on failure, the return value is a nonzero value
indicating the type of error.
 In the prototype, the keyword restrict (part of the C99 standard) means that this pointer
will be the only pointer to the object.

Mutex Destroy Function


pthread_mutex_destroy()

Prototype: int pthread_mutex_destroy(pthread_mutex_t * restrict mutex);

Library: #include <pthread.h>

Purpose: This destroys the mutex object *mutex, so *mutex becomes uninitialized.

Notes:

5
 It is safe to destroy an unlocked mutex but not a locked mutex.
 The object *mutex could be reused, i.e., reinitialized.
 On success, the return value is 0, and on failure, the return value is a nonzero value
indicating the type of error.

Mutex Lock Function


pthread_mutex_lock()

Prototype: int pthread_mutex_lock(pthread_mutex_t * mutex);

Library: #include <pthread.h>

Purpose: This locks *mutex. If necessary, the caller is blocked until *mutex is unlocked (by
someone else) and then &mutex is locked. When the function call ends, *mutex will be in a
locked state.

Notes:

 Suppose we try to relock a locked mutex. Depending on the attributes of the mutex, we
may have an error, or a count may be kept of how many times the caller has locked the
same mutex (and thus will have to unlock it the same number of times).
 On success, the return value is 0, and on failure, the return value is a nonzero value
indicating the type of error.

Mutex Unlock Function


pthread_mutex_unlock()

Prototype: int pthread_mutex_unlock(pthread_mutex_t * mutex);

Library: #include <pthread.h>

Purpose: This unlocks *mutex.

Notes:

 Suppose we try to unlock an unlocked mutex. Depending on the attributes of the


mutex, we may have an error.
 On success, the return value is 0, and on failure, the return value is a nonzero value
indicating the type of error.

6
Producer and Consumer Problem
What is Producer-consumer Problem?
The producer and consumer share a fixed-size buffer used as a queue. The producer’s job is to
generate data and put this in the buffer. The consumer’s job is to consume the data from this
buffer, one at a time.

Problem Statement
How do you make sure that producer doesn’t try to put data in buffer when the buffer is full
and consumer doesn’t try to consumer data when the buffer is empty?
When producer tries to put data into the buffer when it is full, it wastes cpu cycles. The same
is true for consumer it tries to consumer from an empty buffer. It’s better that they go on sleep
in these cases so that the scheduler can schedule another process.
Pseudocode Solution using Semaphore and Mutex
Initialization
Mutex mutex; // Used to provide mutual exclusion for critical section
Semaphore empty = N; // Number of empty slots in buffer
Semaphore full = 0 // Number of slots filled
int in = 0; //index at which producer will put the next data
int out = 0; // index from which the consumer will consume next data
int buffer[N];
Producer Code
while(True) {
// produce an item
wait(empty); // wait/sleep when there are no empty slots
wait(mutex);
buffer[in] = item
in = (in+1)%buffersize;
signal(mutex);
signal(full); // Signal/wake to consumer that buffer has some data and they can consume
now
}
Consumer Code
while(True) {
wait(full); // wait/sleep when there are no full slots
wait(mutex);
item = buffer[out];
out = (out+1)%buffersize;
signal(mutex);
signal(empty); // Signal/wake the producer that buffer slots are emptied, so they can produce
more }

7
PROGRAM:
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <iostream>
using namespace std;

const int MaxItems = 8 ;// Maximum items a producer can produce or a consumer can
consume
const int BufferSize = 6 ;// Size of the buffer

sem_t empty;
sem_t full;
int in = 0;
int out = 0;
int buffer[BufferSize];
pthread_mutex_t mutex;

void *producer(void *pno)


{
int id = *((int *)pno);
int item;
for(int i = 0; i < MaxItems; i++) {
item = rand(); // Produce an random item
sem_wait(&empty);
pthread_mutex_lock(&mutex);
buffer[in] = item;
cout << "Producer "<<id<<": Insert Item "<<buffer[in]<<" at "<<in<<"\n";
in = (in+1)%BufferSize;
pthread_mutex_unlock(&mutex);
sem_post(&full);
sleep(rand()%3);
}
}

void *consumer(void *cno)


{ int id = *((int *)cno);
for(int i = 0; i < MaxItems/2; i++) {
sem_wait(&full);

8
pthread_mutex_lock(&mutex);
int item = buffer[out];
cout << "Consumer " << id << ": Remove Item " <<item<<" from "<<out<<"\n";
out = (out+1)%BufferSize;
pthread_mutex_unlock(&mutex);
sem_post(&empty);
}
}

int main()
{

pthread_t pro,con[2];
pthread_mutex_init(&mutex, NULL);
sem_init(&empty,0,BufferSize);
sem_init(&full,0,0);

int p = 1;
int c[2] = {1,2}; //Just used for numbering the producer and consumer

pthread_create(&pro, NULL,producer, (void *)&p);


pthread_create(&con[0], NULL, consumer, (void *)&c[0]);
pthread_create(&con[1], NULL, consumer, (void *)&c[1]);

pthread_join(pro, NULL);
for(int i = 0; i < 2; i++) {
pthread_join(con[i], NULL);
}

pthread_mutex_destroy(&mutex);
sem_destroy(&empty);
sem_destroy(&full);

return 0;

9
OUTPUT:

Readers & Writers Problem

10
What is readers & writers problem?
Suppose that a database is to be shared among several concurrent processes. Some of these
processes may want only to read the database, whereas others may want to update (that is, to
read and write) the database. We distinguish between these two types of processes by
referring to the former as readers and to the latter as writers. Obviously, if two readers access
the shared data simultaneously, no adverse effects will result. However, if a writer and some
other process (either a reader or a writer) access the database simultaneously, chaos may
ensue.
To ensure that these difficulties do not arise, we require that the writers have exclusive access
to the shared database while writing to the database. This synchronization problem is referred
to as the readers-writers problem.
The readers-writers problem has several variations, all involving priorities. The simplest one,
referred to as the first readers-writers problem, requires that no reader be kept waiting unless a
writer has already obtained permission to use the shared object. In other words, no reader
should wait for other readers to finish simply because a writer is waiting.
Pseudo Code:
Initialization
Semaphore wrt = 1; // A binary semaphore that will be used
both for mutual exclusion and signalling
Mutex mutex; // Provides mutual exclusion when readcount is
being modified
int readcount = 0; // To keep count of the total readers
Writer
wait(wrt);
// Perform write operation
signal(wrt);
READER
wait(mutex);
readcount++;
if(readcount == 1) {
wait(wrt);
}
signal(mutex);
// Perform read operation

wait(mutex);
readcount--;
if(readcount == 0) {
signal(wrt);
}
signal(mutex);

11
 Writer can only write when the readcount is zero or there are no readers waiting.
 If the first reader executes wait(wrt) operation before the writer does, then writer gets
blocked.
 Only when the last reader exits, it calls the signal(wrt) operation signalling writer to
continue
 Similarly, when a writer starts writing(readcount=0) then the first reader gets blocked on
wait(wrt) and this blocks all the readers.

PROGRAM:
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <iostream>
using namespace std;

/* This program provides a possible solution for first readers writers problem using mutex
and semaphore. Here are 10 readers and 5 producers to demonstrate the solution. */

// Global Variables
sem_t wrt;
pthread_mutex_t mutex;
int cnt = 1;
int numreader = 0;

// Writer Thread Code


void* writer(void * wno)
{
//Entry Section
sem_wait(&wrt);
// Critical Section (Writing Section)
cnt = cnt*2;
cout << "Writer " << (*((int *)wno)) << ": modified cnt to " << cnt << endl;
//Exit Section
sem_post(&wrt);
return NULL;

void* reader(void * rno)

12
{
sleep(rand()%3);
//Entry Section
// Reader acquire the lock before modifying numreader
pthread_mutex_lock(&mutex);
numreader++;
if(numreader == 1) {
sem_wait(&wrt); // If this id the first reader, then it will block the writer
}
pthread_mutex_unlock(&mutex);

// Critical Section ( Reading Section )


cout << "Reader" <<*((int *)rno)<<": read cnt as " << cnt << endl;

// Exit Section
// Reader acquire the lock before modifying numreader
pthread_mutex_lock(&mutex);
numreader--;
if(numreader == 0) {
// If this is the last reader, it will wake up the writer
sem_post(&wrt);.
}
pthread_mutex_unlock(&mutex);
return NULL;
}

int main()
{
// Creating variables
pthread_t read[10],write[5];

// Initializing Semaphores
pthread_mutex_init(&mutex, NULL);
sem_init(&wrt,0,1);

//Just used for numbering the producer and consumer


int a[10] = {1,2,3,4,5,6,7,8,9,10};

// Creating Threads
for(int i = 0; i < 10; i++) {
pthread_create(&read[i], NULL,reader, (void *)&a[i]);
}
for(int i = 0; i < 5; i++) {
pthread_create(&write[i], NULL,writer, (void *)&a[i]);
}

13
// Waiting for threads
for(int i = 0; i < 10; i++) {
pthread_join(read[i], NULL);
}
for(int i = 0; i < 5; i++) {
pthread_join(write[i], NULL);
}

// Destroying mutex and semaphore


pthread_mutex_destroy(&mutex);
sem_destroy(&wrt);

return 0;

OUTPUT:

14

You might also like