0% found this document useful (0 votes)
12 views16 pages

Producer Consumer

The document discusses various synchronization mechanisms in operating systems, focusing on read-write locks and the producer-consumer problem. It outlines the implementation of read-write locks, which allow multiple readers or a single writer, and provides examples of how to manage concurrency using semaphores. Additionally, it highlights common bugs in producer-consumer implementations and suggests solutions to ensure deadlock-free and efficient synchronization.

Uploaded by

RishavGoel
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)
12 views16 pages

Producer Consumer

The document discusses various synchronization mechanisms in operating systems, focusing on read-write locks and the producer-consumer problem. It outlines the implementation of read-write locks, which allow multiple readers or a single writer, and provides examples of how to manage concurrency using semaphores. Additionally, it highlights common bugs in producer-consumer implementations and suggests solutions to ensure deadlock-free and efficient synchronization.

Uploaded by

RishavGoel
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/ 16

CS330: Operating Systems

Read-write locks, classical problems


Recap: Spinlocks and semaphores

- Lock implementation using atomic exchange, compare-and-swap, LL–SC


and atomic add
- Ticket spinlocks provide fairness in locking, example implementation
with atomic-add (xadd)
- Software-only lock implementation (Peterson’s solution)
- Semaphore: counting semaphore, binary semaphore (mutex)

Agenda: Read-write locks, producer consumer problem, concurrency bugs


Reader-writer locks
- Allows multiple readers or a single writer to enter the CS
- Example: Insert, delete and lookup operations on a search tree
Reader-writer locks
- Allows multiple readers or a single writer to enter the CS
- Example: Insert, delete and lookup operations on a search tree
struct BST{ struct node{
struct node *root; item_t item;
rwlock_t *lock; struct node *left;
}; struct node*right;
};

void insert(BST *t, item_t item);


void lookup(BST *t, item_t item);
Reader-writer locks
- Allows multiple readers or a single writer to enter the CS
- Example: Insert, delete and lookup operations on a search tree
struct BST{ struct node{
struct node *root; item_t item;
rwlock_t *lock; struct node *left;
}; struct node*right;
};

void insert(BST *t, item_t item); - If multiple threads call lookup( ), they
void lookup(BST *t, item_t item); may traverse the tree in parallel
Implementation of read-write locks
struct rwlock_t{ init_lock(rwlock_t *rL)
Lock read_lock; {
Lock write_lock; init_lock(&rL → read_lock);
int num_readers; init_lock(&rL → write_lock);
} rL → num_readers = 0;
}
Implementation of read-write locks (writers)
struct rwlock_t{ init_lock(rwlock_t *rL)
Lock read_lock; {
Lock write_lock; init_lock(&rL → read_lock);
int num_readers; init_lock(&rL → write_lock);
} rL → num_readers = 0;
}
void write_lock(rwlock_t *rL) void write_unlock(rwlock_t *rL)
{ {
lock(&rL → write_lock); unlock(&rL → write_lock);
} }
- Write lock behavior is same as the typical lock, only one thread allowed to
acquire the lock
Implementation of read-write locks (readers)
struct rwlock_t{
Lock read_lock;
Lock write_lock;
int num_readers;
}
void read_lock(rwlock_t *rL) void read_unlock(rwlock_t *rL)
{ {
lock(&rL → read_lock); lock(&rL → read_lock);
rL → num_readers++; rL → num_readers--;
if(rL → num_readers == 1) if(rL → num_readers == 0)
lock(&rL → write_lock); unlock(&rL → write_lock);
unlock(&rL → read_lock); unlock(&rL → read_lock);
} }
Implementation of read-write locks (readers)
struct rwlock_t{ - The first reader acquires the write lock
Lock read_lock;
prevents writers to acquire lock
Lock write_lock;
int num_readers; - The last reader releases the write lock to
} allow writers
void read_lock(rwlock_t *rL) void read_unlock(rwlock_t *rL)
{ {
lock(&rL → read_lock); lock(&rL → read_lock);
rL → num_readers++; rL → num_readers--;
if(rL → num_readers == 1) if(rL → num_readers == 0)
lock(&rL → write_lock); unlock(&rL → write_lock);
unlock(&rL → read_lock); unlock(&rL → read_lock);
} }
Producer-consumer problem DoConsumerWork( ){
DoProducerWork( ){ while(1){
while(1){ item_t item = consume( );
item_t item = prod_p( ); cons_p(item);
produce(item); }
} }
}
- A buffer of size N, one or more producers and consumers
- Producer produces an element into the buffer (after processing)
- Consumer extracts an element from the buffer and processes it
- Example: A multithreaded web server, network protocol layers etc.
- How to solve this problem using semaphores?
Buggy #1
item_t A[n], pctr=0, cctr = 0;
sem_t empty = sem_init(n), used = sem_init(0);
produce(item_t item){ item_t consume( ) {
sem_wait(&empty); sem_wait(&used);
A[pctr] = item; item_t item = A[cctr];
pctr = (pctr + 1) % n; cctr = (cctr + 1) % n;
sem_post(&used); sem_post(&empty);
} return item;
}
- This solution does not work. What is the issue?
Buggy #1
item_t A[n], pctr=0, cctr = 0;
sem_t empty = sem_init(n), used = sem_init(0);
produce(item_t item){ item_t consume( ) {
sem_wait(&empty); sem_wait(&used);
A[pctr] = item; item_t item = A[cctr];;
pctr = (pctr + 1) % n; cctr = (cctr + 1) % n;
sem_post(&used); sem_post(&empty);
} return item;
}
- This solution does not work. What is the issue?
- The counters (pctr and cctr) are not protected, can cause race conditions
Buggy #2
item_t A[n], pctr=0, cctr = 0; lock_t *L = init_lock( );
sem_t empty = sem_init(n), used = sem_init(0);
produce(item_t item){ item_t consume( ) {
lock(L); sem_wait(&empty); lock(L); sem_wait(&used);
A[pctr] = item; item_t item = A[cctr];;
pctr = (pctr + 1) % n; cctr = (cctr + 1) % n;
sem_post(&used); unlock(L); sem_post(&empty); unlock(L);
} return item;
}
- What is the problem?
Buggy #2
item_t A[n], pctr=0, cctr = 0; lock_t *L = init_lock( );
sem_t empty = sem_init(n), used = sem_init(0);
produce(item_t item){ item_t consume( ) {
lock(L); sem_wait(&empty); lock(L); sem_wait(&used);
A[pctr] = item; item_t item = A[cctr];;
pctr = (pctr + 1) % n; cctr = (cctr + 1) % n;
sem_post(&used); unlock(L); sem_post(&empty); unlock(L);
} return item;
}
- What is the problem?
- Consider empty = 0 and producer has taken lock before the consumer. This
results in a deadlock, consumer waits for L and producer for empty
A working solution
item_t A[n], pctr=0, cctr = 0; lock_t *L = init_lock( );
sem_t empty = sem_init(n), used = sem_init(0);
produce(item_t item){ item_t consume( ) {
sem_wait(&empty); lock(L); sem_wait(&used); lock(L)
A[pctr] = item; item_t item = A[cctr];
pctr = (pctr + 1) % n; cctr = (cctr + 1) % n;
unlock(L); sem_post(&used); unlock(L); sem_post(&empty);
} return item;
}
- The solution is deadlock free and ensures correct synchronization, but very
much serialized (inside produce and consume)
- What if we use separate locks for producer and consumer?
Solution with separate mutexes
item_t A[n], pctr=0, cctr = 0; lock_t *P = init_lock( ), *C=init_lock( );
sem_t empty = sem_init(n), used = sem_init(0);
produce(item_t item){ item_t consume( ) {
sem_wait(&empty); lock(P); sem_wait(&used); lock(C)
A[pctr] = item; item_t item = A[cctr];
pctr = (pctr + 1) % n; cctr = (cctr + 1) % n;
unlock(P); sem_post(&used); unlock(C); sem_post(&empty);
} return item;
}
- Does this solution work?
- Homework: Assume that item is a large object and copy of item takes long
time. How can we perform the copy operation without holding the lock?

You might also like