0% found this document useful (0 votes)
40 views

EE426 OS Lab7 Spring2023

This document summarizes how to use POSIX semaphores, mutexes, and condition variables for thread synchronization in C/C++. It provides examples of using semaphores to synchronize access to shared resources between threads. It also describes condition variables, which allow threads to synchronize based on the value of shared data, avoiding busy polling. Condition variables must be used with a mutex and the typical usage pattern is for one thread to lock the mutex, wait on the condition variable if the predicate is not met, then unlock the mutex. Another thread signals or broadcasts the condition when the predicate changes value. The document provides an incomplete producer-consumer code example and asks to complete it using these synchronization primitives.

Uploaded by

hiba
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views

EE426 OS Lab7 Spring2023

This document summarizes how to use POSIX semaphores, mutexes, and condition variables for thread synchronization in C/C++. It provides examples of using semaphores to synchronize access to shared resources between threads. It also describes condition variables, which allow threads to synchronize based on the value of shared data, avoiding busy polling. Condition variables must be used with a mutex and the typical usage pattern is for one thread to lock the mutex, wait on the condition variable if the predicate is not met, then unlock the mutex. Another thread signals or broadcasts the condition when the predicate changes value. The document provides an incomplete producer-consumer code example and asks to complete it using these synchronization primitives.

Uploaded by

hiba
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 7

M02/EE426 Operating Systems

LAB#7: Threads Synchronization with POSIX Semaphores, Mutexes, and Condition Variables

1. Objectives:
This lab describes how to use semaphore, mutexes, and condition variables to solve various synchronization
problems.

2. POSIX Semaphores
POSIX semaphores are counting semaphores. Operations sem_wait() and sem_post() are equivalent to wait() and
signal(), respectively. POSIX semaphores have the following properties:
 A semaphore is not considered to be owned by a thread – one thread can execute sem_wait() on a semaphore
and another thread can execute sem_post().
 When a semaphore is created, the initial value of the semaphore is specified, where
0 ≤ initial value ≤ SEM_VALUE_MAX.

The following header summarizes how we can use POSIX semaphore:


Header file name #include <semaphore.h>

Semaphore data type sem_t


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

int sem_destroy(sem_t *sem);


int sem_wait(sem_t *sem);
Semaphore Operations int sem_post(sem_t *sem);
int sem_trywait(sem_t *sem);

• sem_init function initializes the semaphore to have the value value. The value parameter cannot
be negative. If the value of pshared is not 0, the semaphore can be used between processes (i.e. the
process that initializes it and by children of that process). Otherwise it can be used only by threads within
the process that initializes it.
• sem_wait is a standard semaphore wait operation. If the semaphore value is 0, the sem_wait blocks
unit it can successfully decrement the semaphore value.
• sem_trywait is similar to sem_wait except that instead of blocking when attempting to decrement
a zero-valued semaphore, it returns -1.
• sem_post is a standard semaphore signal operation.

Example1.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);
}

1
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);
}
/* 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);
}

Example2.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
/* Global variables */
Int x = 0;
sem_t m;
/* Thread function */
void *thread(void *arg)
{
/* critical section */
sem_wait(&m);
/* lock the mutex m */
x = x + 1;
sem_post(&m);
/* unlock the mutex m */
}
void main ()
{
pthread_t tid[10];
int i;
/* semaphore m should be initialized by 1 */
if (sem_init(&m, 0, 1) == -1) {
perror("Could not initialize mylock semaphore");
exit(2);
}
/* create TEN threads */
for (i=0; i<10; i++)
{
if (pthread_create(&tid[i], NULL, thread, NULL) < 0) {
perror("Error: thread cannot be created");
exit(1);
}
}
/* wait for all created thread to terminate */
for (i=0; i<10; i++) pthread_join(tid[i], NULL);
printf("Final value of x is %d\n", x);
exit(0);
}
2
3. Problem: Implementation of the Producer Consumer Problem
Consider the following incomplete producer-consumer code:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>

#define BUFF_SIZE 5 /* total number of slots */


#define NP 3 /* total number of producers */
#define NC 3 /* total number of consumers */
#define NITERS 4 /* number of items produced/consumed */

typedef struct {
int buf[BUFF_SIZE]; /* shared var */
int in; /* buf[in%BUFF_SIZE] is the first empty slot */
int out; /* buf[out%BUFF_SIZE] is the first full slot */
sem_t full; /* keep track of the number of full spots */
sem_t empty; /* keep track of the number of empty spots */
sem_t mutex; /* enforce mutual exclusion to shared data */
} sbuf_t;

sbuf_t shared;

void *Producer(void *arg)


{
int i, item, index;

index = (int)arg;

for (i=0; i < NITERS; i++) {

/* Produce item */
item = i;

/* Prepare to write item to buf */

/* If there are no empty slots, wait */


sem_wait(&shared.empty);
/* If another thread uses the buffer, wait */
sem_wait(&shared.mutex);
shared.buf[shared.in] = item;
shared.in = (shared.in+1)%BUFF_SIZE;
printf("[P%d] Producing %d ...\n", index, item); fflush(stdout);
/* Release the buffer */
sem_post(&shared.mutex);
/* Increment the number of full slots */
sem_post(&shared.full);

/* Interleave producer and consumer execution */


if (i % 2 == 1) sleep(1);
}
return NULL;
}

void *Consumer(void *arg)


{
/* Fill in the code here */
}

int main()
{
pthread_t idP, idC;
int index;

3
sem_init(&shared.full, 0, 0);
sem_init(&shared.empty, 0, BUFF_SIZE);

/* Insert code here to initialize mutex*/

for (index = 0; index < NP; index++)


{
/* Create a new producer */
pthread_create(&idP, NULL, Producer, (void*)index);
}

/* Insert code here to create NC consumers */

pthread_exit(NULL);
}

1. Complete the above code to implement a solution to the Producer-Consumer problem using Posix threads and
semaphores.
2. Compile and run your program and observe the output.

4. Condition Variables:
4.1. Overview
Condition variables provide yet another way for threads to synchronize. While mutexes implement synchronization by
controlling thread access to data, condition variables allow threads to synchronize based upon the actual value of data.
Without condition variables, the programmer would need to have threads continually polling (possibly in a critical
section), to check if the condition is met. This can be very resource consuming since the thread would be continuously
busy in this activity. A condition variable is a way to achieve the same goal without polling.
A condition variable is always used in conjunction with a mutex lock.
The typical sequence for using condition variables:
 Create and initialize a condition variable
 Create and initialize an associated mutex
Define a predicate variable (variable whose condition must be checked)
A thread does work up to the point where it needs a certain condition to occur (such as the predicate must reach a
specified value). It then "waits" on a condition variable by:
 Locking the mutex
 While predicate is unchanged wait on condition variable
 Unlocking the mutex
Another thread does work which results in the waited for condition to occur (such as changing the value of the
predicate). Other waiting threads are "signalled" when this occurs by:
 Locking the mutex
 Changing the predicate
 Signalling on the condition variable
 Unlocking the mutex
4.2. Creating / Destroying Condition Variables:
pthread_cond_init ( pthread_cond_t condition, pthread_condattr_t attr)
pthread_cond_destroy ( pthread_cond_t condition)
pthread_condattr_init ( pthread_condattr_t attr )
pthread_condattr_destroy ( pthread_condattr_t attr )
pthread_cond_init( ) creates and initializes a new condition variable object. The ID of the created condition variable is
returned to the calling thread through the condition parameter.
Condition variables must be of type pthread_cond_t .
The optional attr object is used to set condition variable attributes. There is only one attribute defined for condition
variables: process-shared, which allows the condition variable to be seen by threads in other processes. The attribute
object, if used, must be of type pthread_condattr_t (may be specified as NULL to accept defaults). Currently, the
attributes type attr is ignored in the AIX implementation of pthreads; use NULL. If implemented, the
pthread_condattr_init( ) and pthread_condattr_destroy( ) routines are used to create and destroy condition variable
attribute objects. pthread_cond_destroy( ) should be used to free a condition variable that is no longer needed.

4
4.3. Waiting / Destroying Condition Variables :
pthread_cond_wait ( pthread_cond_t condition, pthread_mutex_t mutex )
pthread_cond_signal ( pthread_cond_t condition )
pthread_cond_broadcast ( pthread_cond_t condition )
pthread_cond_wait( ) blocks the calling thread until the specified condition is signalled. This routine should be called
while mutex is locked, and it will automatically release the mutex while it waits.
The pthread_cond_signal( ) routine is used to signal (or wake up) another thread which is waiting on the condition
variable. It should be called after mutex is locked.
The pthread_cond_broadcast( ) routine should be used instead of pthread_cond_signal( ) if more than one thread is in
a blocking wait state.
It is a logical error to call pthread_cond_signal( ) before calling pthread_cond_wait( ).

Example3.c:
#include <pthread.h>
#include <stdio.h>
/* This is the initial thread routine */
void* compute_thread (void*);
/* This is the lock for thread synchronization */
pthread_mutex_t my_sync;
/* This is the condition variable */
pthread_cond_t rx;
#define TRUE 1
#define FALSE 0
/* this is the Boolean predicate */
int thread_done = FALSE;

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);
/* Initialize the condition variable (default attr) */
pthread_cond_init (&rx, NULL);
/* Create another thread. ID is returned in &tid */
/* The last parameter is passed to the thread function */ pthread_create(&tid,
&attr, compute_thread, hello);
/* wait until the thread does its work */
pthread_mutex_lock(&my_sync);
while (!thread_done)
pthread_cond_wait(&rx, &my_sync);
/* When we get here, the thread has been executed */
printf(thread);
printf("\n");
pthread_mutex_unlock(&my_sync);
exit(0);
}

/* The thread to be run by create_thread */


void* compute_thread(void* dummy) {
/* Lock the mutex - the cond_wait has unlocked it */
pthread_mutex_lock (&my_sync);
printf(dummy);
/* set the predicate and signal the other thread */
thread_done = TRUE;
pthread_cond_signal (&rx);
pthread_mutex_unlock (&my_sync);
return;
}
5
Example4.c:
#include<stdio.h>
#include<pthread.h>
void *increment(void*);
void *watch(void*);
pthread_mutex_t my_mutex;
pthread_cond_t w_cond;
int count=0;
main()
{
pthread_t tid1, tid2, tid3;
pthread_mutex_init (&my_mutex, NULL);
pthread_cond_init(&w_cond, NULL);
pthread_create(&tid1,NULL,watch, NULL);
pthread_create(&tid2,NULL,increment, NULL);
pthread_create(&tid3,NULL,increment, NULL);
pthread_join(tid1,NULL);
pthread_mutex_destroy(&my_mutex);
pthread_cond_destroy(&w_cond);
printf("Parent is DONE ....\n");
exit(0);
}

void* increment(void *dummy)


{
while(count<10)
{
pthread_mutex_lock(&my_mutex);
printf("Cound =%d by thread %lu\n",++count,pthread_self());
if(count==10)
pthread_cond_signal(&w_cond);
pthread_mutex_unlock(&my_mutex);
sleep(1);
}
return;
}

void* watch(void *dummy)


{
pthread_mutex_lock(&my_mutex);
pthread_cond_wait(&w_cond,&my_mutex);
pthread_mutex_unlock(&my_mutex);
printf("Watch is DONE...\n");
return;
}

5. Problem: Implementation of the Dining Philosophers Problem


In the lecture materials, we provide an outline of a solution to the dining-philosophers problem using monitors. This
problem will require implementing a solution using Pthreads mutex locks and condition variables.

5.1. The Philosophers


Begin by creating five philosophers, each identified by a number 0 . . 4. Each philosopher will run as a separate
thread. Philosophers alternate between thinking and eating.
To simulate both activities, have the thread sleep for a random period between one and three seconds. When a
philosopher wishes to eat, she invokes the function
Pickup_forks(int philosopher number)
Where philosopher number identifies the number of the philosopher wishing to eat.
When a philosopher finishes eating, he invokes
Return_forks(int philosopher number)

6
5.2. Pthreads Condition Variables
In this problem, condition variables are used within the context of a monitor, which provides a locking mechanism to
ensure data integrity. Since Pthreads is typically used in C programs—and since C does not have a monitor— we
accomplish locking by associating a condition variable with a mutex lock.

5.3. What to do
Based on the monitor-based solution to the dining-philosophers problem covered in the lecture, write a complete
program that illustrates how Pthreads mutex locks and condition variables can be used to solve the above problem.

You might also like