#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include "synch.h"
#include "rwlock.h"
#define NUM_READERS 5
#define NUM_WRITERS 2
#define NUM_ITERATIONS 10
static const char *reader_names[] = {
"Robin", "Riley", "Raven", "Reese", "Renee",
"Reena", "River", "Rohan", "Ronan", "Rania"
};
static const char *writer_names[] = {
"Wendy", "Wayne", "Wyatt", "Wilma", "Wylie",
"Wanda", "Wells", "Warda", "Woody", "Walid",
};
struct shared_data {
int value;
struct rwlock *rw_lock;
};
struct shared_data data;
void *
reader(void *arg)
{
const char *id = (char *) arg;
for (int i = 0; i < NUM_ITERATIONS; i++) {
printf("Reader %s tries to acquire lock\n", id);
rwlock_readlock(data.rw_lock);
printf("Reader %s acquires lock, reads value: %d\n", id,
data.value);
usleep(10000 + rand() % 90000); // Sleep for 10-100ms
rwlock_unlock(data.rw_lock);
printf("Reader %s releases lock\n", id);
usleep(10000 + rand() % 90000); // Sleep for 10-100ms
}
return NULL;
}
void *
writer(void *arg)
{
const char *id = (char *) arg;
for (int i = 0; i < NUM_ITERATIONS; i++) {
printf("Writer %s tries to acquire lock\n", id);
rwlock_writelock(data.rw_lock);
data.value++;
printf("Writer %s acquires lock, updates value to: %d\n", id,
data.value);
usleep(10000 + rand() % 90000); // Sleep for 10-100ms
rwlock_unlock(data.rw_lock);
printf("Writer %s releases lock\n", id);
usleep(10000 + rand() % 90000); // Sleep for 10-100ms
}
return NULL;
}
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
int
main(void)
{
assert(ARRAY_SIZE(reader_names) >= NUM_READERS);
assert(ARRAY_SIZE(writer_names) >= NUM_WRITERS);
pthread_t readers[NUM_READERS];
pthread_t writers[NUM_WRITERS];
data.value = 0;
data.rw_lock = rwlock_create("shared_data_lock");
if (data.rw_lock == NULL) {
fprintf(stderr, "Failed to create reader-writer lock\n");
return 1;
}
// Create reader threads
for (int i = 0; i < NUM_READERS; i++) {
if (pthread_create(&readers[i], NULL, reader,
(void *) reader_names[i]) != 0) {
fprintf(stderr, "Failed to create reader thread\n");
return 1;
}
}
// Create writer threads
for (int i = 0; i < NUM_WRITERS; i++) {
if (pthread_create(&writers[i], NULL, writer,
(void *) writer_names[i]) != 0) {
fprintf(stderr, "Failed to create writer thread\n");
return 1;
}
}
// Wait for all threads to complete
for (int i = 0; i < NUM_READERS; i++) {
pthread_join(readers[i], NULL);
}
for (int i = 0; i < NUM_WRITERS; i++) {
pthread_join(writers[i], NULL);
}
rwlock_destroy(data.rw_lock);
printf("Final value: %d\n", data.value);
return 0;
}
#include <assert.h>
#include <stdlib.h>
#include "synch.h"
#include "rwlock.h"
struct rwlock {
struct lock *lock;
struct cv *read_cv;
struct cv *write_cv;
int readers;
int writers;
int waiting_writers;
};
/*
* rwlock_create: Creates and initializes a new reader-writer lock.
*
* The lock starts in an unlocked state with no active readers or writers.
*/
struct rwlock *
rwlock_create(const char *name)
{
struct rwlock *rw = malloc(sizeof(struct rwlock));
if (rw == NULL) {
return NULL;
}
rw->lock = lock_create("rwlock_internal");
rw->read_cv = cv_create("rwlock_read_cv");
rw->write_cv = cv_create("rwlock_write_cv");
if (rw->lock == NULL || rw->read_cv == NULL || rw->write_cv == NULL) {
rwlock_destroy(rw);
return NULL;
}
rw->readers = 0;
rw->writers = 0;
rw->waiting_writers = 0;
return rw;
}
/*
* rwlock_destroy: Cleans up and frees all resources associated with the lock.
*
* Should only be called when no threads are using or waiting on the lock.
*/
void
rwlock_destroy(struct rwlock *rw)
{
if (rw != NULL) {
if (rw->lock != NULL)
lock_destroy(rw->lock);
if (rw->read_cv != NULL)
cv_destroy(rw->read_cv);
if (rw->write_cv != NULL)
cv_destroy(rw->write_cv);
free(rw);
}
}
/*
* rwlock_readlock: Acquires the lock in read mode.
*
* Multiple readers can hold the lock simultaneously, but not while a writer
* is waiting. This prevents writer starvation.
*/
void
rwlock_readlock(struct rwlock *rw)
{
lock_acquire(rw->lock);
/*
* Lock out readers not only when there are active writers, but also
* when there are writers waiting. This prevents writer starvation.
*/
while (rw->writers > 0 || rw->waiting_writers > 0) {
cv_wait(rw->read_cv, rw->lock);
}
rw->readers++;
lock_release(rw->lock);
}
/*
* rwlock_writelock: Acquires the lock in write mode.
*
* Only one writer can hold the lock, and no readers can be active.
*/
void
rwlock_writelock(struct rwlock *rw)
{
lock_acquire(rw->lock);
rw->waiting_writers++;
while (rw->readers > 0 || rw->writers > 0) {
cv_wait(rw->write_cv, rw->lock);
}
rw->waiting_writers--;
rw->writers++;
lock_release(rw->lock);
}
/*
* rwlock_unlock: Releases the lock, whether held in read or write mode.
*
* If writers are waiting, they get priority. Otherwise, all waiting readers
* are released.
*/
void
rwlock_unlock(struct rwlock *rw)
{
lock_acquire(rw->lock);
assert(rw->readers > 0 || rw->writers > 0);
if (rw->writers > 0) {
rw->writers--;
} else if (rw->readers > 0) {
rw->readers--;
}
if (rw->readers == 0 && rw->writers == 0) {
if (rw->waiting_writers > 0) {
/* Give priority to a waiting writer */
cv_signal(rw->write_cv, rw->lock);
} else {
/* No writers waiting, wake up all readers */
cv_broadcast(rw->read_cv, rw->lock);
}
}
lock_release(rw->lock);
}
#ifndef RWLOCK_H
#define RWLOCK_H
struct rwlock;
// declare functions
struct rwlock *rwlock_create(const char *name);
void rwlock_destroy(struct rwlock *rw);
void rwlock_readlock(struct rwlock *rw);
void rwlock_writelock(struct rwlock *rw);
void rwlock_unlock(struct rwlock *rw);
#endif // RWLOCK_H
#ifndef SYNCH_H
#define SYNCH_H
struct lock;
struct lock *lock_create(const char *name);
int lock_do_i_hold(struct lock *lock);
void lock_acquire(struct lock *lock);
void lock_release(struct lock *lock);
void lock_destroy(struct lock *lock);
struct cv;
struct cv *cv_create(const char *name);
void cv_wait(struct cv *cv, struct lock *lock);
void cv_signal(struct cv *cv, struct lock *lock);
void cv_broadcast(struct cv *cv, struct lock *lock);
void cv_destroy(struct cv *cv);
#endif // SYNCH_H
/*
* You don't need to look at this file. It defines the lock and condition
* variable interfaces with error checking analogous to the ones found in
* in OS/161.
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "synch.h"
#define ERR_CHECK(result, msg, ...) \
if (result != 0) { \
fprintf(stderr, "Error: " msg ": %s\n", ##__VA_ARGS__, \
strerror(result)); \
exit(1); \
}
#define COND_CHECK(cond, msg, ...) \
if (!(cond)) { \
fprintf(stderr, "Error: " msg "\n", ##__VA_ARGS__); \
exit(1); \
}
struct lock {
const char *name;
pthread_mutex_t posix_mutex;
pthread_t lock_owner;
};
struct lock *
lock_create(const char *name)
{
struct lock *lock = malloc(sizeof(struct lock));
COND_CHECK(lock != NULL, "Failed to allocate memory for lock");
lock->name = name;
int result = pthread_mutex_init(&(lock->posix_mutex), NULL);
ERR_CHECK(result, "Failed to initialize mutex");
lock->lock_owner = (pthread_t) 0;
return lock;
}
int
lock_do_i_hold(struct lock *lock)
{
COND_CHECK(lock != NULL, "Attempted to check NULL lock");
return pthread_self() == lock->lock_owner;
}
void
lock_acquire(struct lock *lock)
{
COND_CHECK(lock != NULL, "Attempted to acquire NULL lock");
int result = pthread_mutex_lock(&(lock->posix_mutex));
ERR_CHECK(result, "Failed to acquire lock %s", lock->name);
lock->lock_owner = pthread_self();
}
void
lock_release(struct lock *lock)
{
COND_CHECK(lock != NULL, "Attempted to release NULL lock");
COND_CHECK(lock_do_i_hold(lock),
"Attempted to release lock %s not held by current thread",
lock->name);
lock->lock_owner = (pthread_t) 0;
int result = pthread_mutex_unlock(&(lock->posix_mutex));
ERR_CHECK(result, "Failed to release lock %s", lock->name);
}
void
lock_destroy(struct lock *lock)
{
COND_CHECK(lock != NULL, "Attempted to destroy NULL lock");
int result = pthread_mutex_destroy(&(lock->posix_mutex));
ERR_CHECK(result, "Failed to destroy lock %s", lock->name);
free(lock);
}
/* --- CONDITION VARIABLES ----------------------------------------------- */
struct cv {
const char *name;
pthread_cond_t posix_cv;
};
struct cv *
cv_create(const char *name)
{
struct cv *cv = malloc(sizeof(struct cv));
COND_CHECK(cv != NULL, "Failed to allocate memory for cv %s", name);
int result = pthread_cond_init(&(cv->posix_cv), NULL);
ERR_CHECK(result, "Failed to initialize condition variable %s", name);
cv->name = name;
return cv;
}
void
cv_wait(struct cv *cv, struct lock *lock)
{
COND_CHECK(cv != NULL, "Attempted to wait on NULL cv");
COND_CHECK(lock != NULL, "Attempted to wait on NULL lock");
COND_CHECK(lock_do_i_hold(lock),
"Attempted to wait on cv %s without holding lock %s",
cv->name, lock->name);
int result = pthread_cond_wait(&(cv->posix_cv), &(lock->posix_mutex));
ERR_CHECK(result, "Failed to wait on cv %s", cv->name);
lock->lock_owner = pthread_self();
}
void
cv_signal(struct cv *cv, struct lock *lock)
{
COND_CHECK(cv != NULL, "Attempted to signal NULL cv");
COND_CHECK(lock != NULL, "Attempted to signal on NULL lock");
COND_CHECK(lock_do_i_hold(lock),
"Attempted to signal cv %s without holding lock %s",
cv->name, lock->name);
int result = pthread_cond_signal(&(cv->posix_cv));
ERR_CHECK(result, "Failed to signal cv %s", cv->name);
}
void
cv_broadcast(struct cv *cv, struct lock *lock)
{
COND_CHECK(cv != NULL, "Attempted to broadcast NULL cv");
COND_CHECK(lock != NULL, "Attempted to broadcast on NULL lock");
COND_CHECK(lock_do_i_hold(lock),
"Attempted to broadcast cv %s without holding lock %s",
cv->name, lock->name);
int result = pthread_cond_broadcast(&(cv->posix_cv));
ERR_CHECK(result, "Failed to broadcast cv %s", cv->name);
}
void
cv_destroy(struct cv *cv)
{
COND_CHECK(cv != NULL, "Attempted to destroy NULL cv");
int result = pthread_cond_destroy(&(cv->posix_cv));
ERR_CHECK(result, "Failed to destroy cv %s", cv->name);
free(cv);
}