Question: We are expected to do the following in C programming language via a linux terminal. Please only modify rwlock.c not the other 2 files, thank

We are expected to do the following in C programming language via a linux terminal. Please only modify rwlock.c not the other 2 files, thank you.
Here are the given C files, please only implement code in rwlock.c.
----------------------->>>>>> rwlock.c:
#include
#include "error.h"
#include "rwlock.h"
int rwlock_rdlock(rwlock* lk) {
// TODO implement me!
return 0;
}
int rwlock_wrlock(rwlock* lk) {
// TODO implement me!
return 0;
}
int rwlock_rdunlock(rwlock* lk) {
// TODO implement me!
return 0;
}
int rwlock_wrunlock(rwlock* lk) {
// TODO implement me!
return 0;
}
int rwlock_init(rwlock* lk) {
int st;
/* Initialize mutex, check for errors */
st = pthread_mutex_init(&lk->mutex, NULL);
if(st) {
pthread_mutex_destroy(&lk->mutex);
return st;
}
/* Initialize condition variables, check for errors */
st = pthread_cond_init(&lk->readcond, NULL);
if(st) {
pthread_cond_destroy(&lk->readcond);
return st;
}
st = pthread_cond_init(&lk->writecond, NULL);
if(st) {
pthread_cond_destroy(&lk->writecond);
return st;
}
/* Initialize all other variables to zero */
lk->r_active = lk->w_active = 0;
lk->r_wait = lk->w_wait = 0;
lk->is_dead = 0;
/* Done! */
return 0;
}
int rwlock_destroy(rwlock* lk) {
int st,str,stw,stu;
/* Make sure the lock hasn't already been destroyed */
if(lk->is_dead) return ERROR_INVALID;
st = pthread_mutex_lock(&lk->mutex);
if(st) return st;
/* Ensure no one is using this lock */
if(lk->r_active > 0 || lk->w_active > 0) {
pthread_mutex_unlock(&lk->mutex);
return ERROR_BUSY;
}
/* Ensure no one is waiting to use this lock */
if(lk->r_wait != 0 || lk->w_wait != 0) {
pthread_mutex_unlock(&lk->mutex);
return ERROR_BUSY;
}
/* Destroy all of the POSIX stuff */
lk->is_dead = 1;
str = pthread_cond_destroy(&lk->readcond);
stw = pthread_cond_destroy(&lk->writecond);
st = pthread_mutex_unlock(&lk->mutex);
if(st) return st;
st = pthread_mutex_destroy(&lk->mutex);
return (st ? st : (str ? str : stw));
}
--------------------------------------------------------------->>>> rwlock.h
#ifndef __RWLOCK_H__
#define __RWLOCK_H__
#include
#define ERROR_INVALID -1
#define ERROR_BUSY -2
typedef struct {
pthread_mutex_t mutex; /* for controlling access to all the other data */
pthread_cond_t readcond; /* condition variable to wait for read */
pthread_cond_t writecond; /* condition variable to wait for write */
int r_active; /* # readers active */
int w_active; /* is writer active? */
int r_wait; /* # readers waiting */
int w_wait; /* # writers waiting */
int is_dead; /* 0 if alive, 1 if destroyed */
} rwlock;
/* Constructor and destructor */
int rwlock_init(rwlock* lk);
int rwlock_destroy(rwlock* lk);
/* Lock and unlock functions */
int rwlock_rdlock(rwlock* lk);
int rwlock_wrlock(rwlock* lk);
int rwlock_rdunlock(rwlock* lk);
int rwlock_wrunlock(rwlock* lk);
#endif
------------------------------------------------------------------>>> tester.c
#include
#include
#include
#include
#include
#include "rwlock.h"
typedef struct {
char* text;
} msg;
typedef struct {
rwlock* lk;
int index;
msg* msg;
} argstruct;
static char* TEXT = "Hello world!";
/* Stolen from: https://stackoverflow.com/a/6852396 */
/* Used to generate random integers uniformly in the
* range [0, max) without bias towards lower bits.
* Overkill? Yes. Needed? No. Do it anyway? Of course. */
long random_at_most(long max) {
unsigned long
num_bins = (unsigned long) max + 1,
num_rand = (unsigned long) RAND_MAX + 1,
bin_size = num_rand / num_bins,
defect = num_rand % num_bins;
long x;
do
x = random();
while (num_rand - defect
return x/bin_size;
}
void* writing(void* _arg) {
argstruct* arg = (argstruct*) _arg;
rwlock* lk = arg->lk;
int i, len, st;
while(1) {
len = random_at_most(strlen(TEXT));
printf("writer %d waiting... ", arg->index); fflush(stdout);
st = rwlock_wrlock(lk);
if(st) {
printf("writer %d: rwlock_wrlock error: %d ", arg->index, st); fflush(stdout);
exit(200);
}
printf("writer %d got lock! starting w/ len=%d! ", arg->index, len); fflush(stdout);
char* newmsg = (char*) malloc((len+1) * sizeof(char));
if(!newmsg) exit(arg->index);
for(i = 0; i
newmsg[i] = TEXT[i];
newmsg[len] = '\0';
free(arg->msg->text);
st = random_at_most(500)+500;
printf("writer %d waiting %d milliseconds! ", arg->index, st); fflush(stdout);
usleep(1000*st);
arg->msg->text = newmsg;
printf("writer %d : new msg = %s ", arg->index, arg->msg->text); fflush(stdout);
printf("writer %d unlocking... ", arg->index); fflush(stdout);
st = rwlock_wrunlock(lk);
if(st) {
printf("writer %d: rwlock_wrunlock error: %d ", arg->index, st); fflush(stdout);
exit(201);
}
usleep(500*1000);
}
pthread_exit(NULL);
}
void* reading(void* _arg) {
argstruct* arg = (argstruct*) _arg;
rwlock* lk = arg->lk;
int st;
while(1) {
printf("reader %d waiting... ", arg->index); fflush(stdout);
st = rwlock_rdlock(lk);
if(st) {
printf("reader %d: rwlock_rdlock error: %d ", arg->index, st); fflush(stdout);
exit(300);
}
printf("reader %d got lock! ", arg->index); fflush(stdout);
st = random_at_most(500)+500;
printf("reader %d waiting %d milliseconds! ", arg->index, st); fflush(stdout);
usleep(1000*st);
printf("reader %d : msg = %s ", arg->index, arg->msg->text); fflush(stdout);
printf("reader %d unlocking... ", arg->index); fflush(stdout);
st = rwlock_rdunlock(lk);
if(st) {
printf("reader %d: rwlock_rdunlock error: %d ", arg->index, st); fflush(stdout);
exit(301);
}
usleep(1000*random_at_most(750));
}
pthread_exit(NULL);
}
int main(int argc, char* argv[]) {
int i, st;
int numreaders;
int numwriters;
/* Parse program args */
if(argc != 3) {
printf("usage: ./tester ");
return 0;
}
numreaders = atoi(argv[1]);
numwriters = atoi(argv[2]);
pthread_t rdrs[numreaders];
pthread_t wtrs[numwriters];
argstruct rdrargs[numreaders];
argstruct wtrargs[numwriters];
pthread_mutex_t mutex;
rwlock lk;
/* Initialize rwlock */
if(st = rwlock_init(&lk)) {
printf("lock init failed! err: %d ", st);
return 1000;
}
if(st = rwlock_destroy(&lk)) {
printf("lock destroy failed! err: %d ", st);
return 1001;
}
if(st = rwlock_init(&lk)) {
printf("lock init2 failed! err: %d ", st);
return 10002;
}
/* Initialize first message to be handled */
msg* arg = (msg*) malloc(sizeof(msg));
arg->text = (char*) malloc((strlen(TEXT)+1) * sizeof(char));
for(i = 0; i
arg->text[i] = TEXT[i];
arg->text[strlen(TEXT)] = '\0';
/* Create reading threads */
for(i = 0; i
rdrargs[i].msg = arg;
rdrargs[i].index = i;
rdrargs[i].lk = &lk;
st = pthread_create(&rdrs[i], NULL, reading, (void*)&rdrargs[i]);
if(st) {
printf("failed to create reader %d! err: %d ", i, st);
return 2;
}
}
/* Create writing threads */
for(i = 0; i
wtrargs[i].msg = arg;
wtrargs[i].index = i;
wtrargs[i].lk = &lk;
st = pthread_create(&wtrs[i], NULL, writing, (void*)&wtrargs[i]);
if(st) {
printf("failed to create writer %d! err: %d ", i, st);
return 3;
}
}
/* This shouldn't happen if everything works. Will happen if threads crash */
void** ret;
for(i = 0; i
st = pthread_join(rdrs[i], ret);
if(st) {
printf("error when joining reader threads. err: %d ", st);
return 4;
}
}
for(i = 0; i
st = pthread_join(wtrs[i], ret);
if(st) {
printf("error when joining writer threads. err: %d ", st);
return 5;
}
}
/* Clean up! */
free(arg->text);
free(arg);
/* Done! (if something broke, we'll get here) */
return 0;
}
Consider a program with multiple worker threads and a shared data structure. By using a mutex, we could ensure that only one thread reads from or writes to the shared data at a time. However, we would gain a significant speedup if we could allow multiple threads to read from the shared data concurrently and only restrict access once a worker needs to modify the shared data. This is where read-write locks come in. More poignantly, the motivation for read-write locks is twofold. First, only one thread should be able to modify shared data at any one time. This was the reason we needed to introduce mutexes in the first place; non-atomic, concurrent modification of shared data leads to undefined program behavior. Second, there is no danger in multiple threads having read-only access to a piece of shared data as long as no other thread is modifying it. Again, problems only arise when one of the threads needs to modify the shared data; concurrent reads are just fine Your task is to implement a read-write lock using a POSIX mutex and POSIX conditions. The structure to use as well as the read-write lock API are provided in rwlock.h. You should provide implementations for the two lock functions and two unlock functions in rwlock.c. The initialization and destruction functions are already provided to you. To aid in this task, you have been provided a second C file: tester. When run as?tester MX, this program will spawn M reading threads and N writing threads, each of which will try to read from and write to a shared dat:a structure, i.e. a string. The program will run until it is terminated by sending it the termination signal via Ctl+C. When testing your read-write lock implementation, you should check to ensure that only one thread has the write lock at a time, that no threads have the write lock when others are reading, and that once a thread tries to get the write lock, it will prevent any further threads from getting the read lock. The last point is referred to as write-starvation. If preference is not given to a writing worker over a reading worker while both are waiting for a lock, it is possible that only reading workers will ever get the lock thus starving the writing workers and preventing them from ever getting work done. This is particularly noticeable when there are many more readers than writers. You wouldn't starve your dog, so don't starve your writers! You should not modify rwlockh or tester.c. You should only provide implementations for the aforementioned func tions. As usual, ensure your implementation does not produce any memory errors when run! Consider a program with multiple worker threads and a shared data structure. By using a mutex, we could ensure that only one thread reads from or writes to the shared data at a time. However, we would gain a significant speedup if we could allow multiple threads to read from the shared data concurrently and only restrict access once a worker needs to modify the shared data. This is where read-write locks come in. More poignantly, the motivation for read-write locks is twofold. First, only one thread should be able to modify shared data at any one time. This was the reason we needed to introduce mutexes in the first place; non-atomic, concurrent modification of shared data leads to undefined program behavior. Second, there is no danger in multiple threads having read-only access to a piece of shared data as long as no other thread is modifying it. Again, problems only arise when one of the threads needs to modify the shared data; concurrent reads are just fine Your task is to implement a read-write lock using a POSIX mutex and POSIX conditions. The structure to use as well as the read-write lock API are provided in rwlock.h. You should provide implementations for the two lock functions and two unlock functions in rwlock.c. The initialization and destruction functions are already provided to you. To aid in this task, you have been provided a second C file: tester. When run as?tester MX, this program will spawn M reading threads and N writing threads, each of which will try to read from and write to a shared dat:a structure, i.e. a string. The program will run until it is terminated by sending it the termination signal via Ctl+C. When testing your read-write lock implementation, you should check to ensure that only one thread has the write lock at a time, that no threads have the write lock when others are reading, and that once a thread tries to get the write lock, it will prevent any further threads from getting the read lock. The last point is referred to as write-starvation. If preference is not given to a writing worker over a reading worker while both are waiting for a lock, it is possible that only reading workers will ever get the lock thus starving the writing workers and preventing them from ever getting work done. This is particularly noticeable when there are many more readers than writers. You wouldn't starve your dog, so don't starve your writers! You should not modify rwlockh or tester.c. You should only provide implementations for the aforementioned func tions. As usual, ensure your implementation does not produce any memory errors when run
Step by Step Solution
There are 3 Steps involved in it
Get step-by-step solutions from verified subject matter experts
