Implementation of a Monitor
Assigned: Feb 20, 2020
Due: Mar 30, 2020 23:59:59
This programming assignment is to get you familiar with the synchronization constructs we’ve been discussing in class – mutexes, semaphores, and monitors. There are two problems in this assignment: solving the producer/consumer problem with 1) semaphores, and 2) your own custom monitor.
Do the following:
Create a directory called <UNL username>_pa3
(replace <UNL username>
with your UNL username).
Create subdirectories: prob1
and prob2
under <UNL username>_pa3
.
Place your solutions in the proper directory. Provide a README
file for each problem in each directory. Each README
file should specify:
Add a separate Makefile
for both problems. The name of the executable produced by the first part and second part should be part1
and part2
respectively. Note that if you don’t provide a Makefile we can’t/won’t compile your code and you won’t pass any tests.
You should have the following directory structure:
<UNL username>_pa3
|-----prob1
| |----Makefile
| |----README
| |----part1.c
|-----prob2
|----Makefile
|----README
|----part2.c
Zip the directory <UNL username>_pa3
and submit.
Use web handin to hand in your assignment. Submit a single zip file, <UNL username>_pa3.zip
(e.g., jdoe2_pa3.zip
).
To make the program easier to evaluate, you will need to implement additional switches to specify the buffer length, number of producers and the number of consumers as indicated below.
Switch | Specifies |
---|---|
-b | Buffer length in bytes |
-p | Number of producer threads |
-c | Number of consumer threads |
-i | Number of items to insert |
As an example: cse> ./part1 –b 1000 –p 10 –c 10 -i 100000
should create a buffer of length 1,000 bytes, 10 producer threads, 10 consumer threads, and insert 10,000 items.
Whenever an item is inserted or removed from the buffer, a message needs to be printed to the screen with the following convention:
"p:<%u>, item: %c, at %d", threadid, item, index
This is to indicate the producer thread with threadid
inserted an item at the index in buffer. We will output a similar message for the consumer when an item is consumed, replacing "p"
with "c"
.
The producer thread should exit when it has no items to be inserted to buffer. The consumer thread should exit when the buffer is empty and it has consumed all the items. Manage this by using a counter for the number of items inserted and removed.
As usual we have provided you with precompiled binaries that run on the CSE servers to observe the correct input/output and compare against your solution.
Your program will be graded according to the following rubric:
Command | ND? | NRC? | ICM? | Total |
---|---|---|---|---|
./part1 -b 1 -p 5 -c 5 -i 10 |
5 | 5 | 5 | 15 |
./part1 -b 1000 -p 20 -c 20 -i 10000 |
5 | 5 | 5 | 15 |
Part 1 README | 5 | |||
Part 1 Total = 35 points | ||||
./part2 -b 1 -p 5 -c 5 -i 10 |
5 | 5 | 5 | 15 |
./part2 -b 4 -p 30 -c 30 -i 10 |
5 | 5 | 5 | 15 |
./part2 -b 10 -p 1 -c 10 -i 20 |
5 | 5 | 5 | 15 |
./part2 -b 1000 -p 20 -c 20 -i 10000 |
5 | 5 | 5 | 15 |
Part 2 README | 5 | |||
Part 2 Total = 65 points |
where
-i
.Additionally if the naming convention described in the Submission section above is not followed you lose 10 points. If your code does not compile on the CSE servers you can only get the points for the README, which is 10 max. In other words, if your code doesn’t compile on the CSE servers you will get 0 for all tests. Please check that your code both compiles and runs on the CSE servers!!! Don’t wait until it works completely on your own machine before testing on the CSE servers. Test on the CSE servers at regular milestones in your development.
Finally, you can combine the grep
command with the wc
command to get the count of producers or consumers. For example, for producers:
./part2 -b 1000 -p 20 -c 20 -i 10000 | grep p: | wc -l
And for consumers:
./part2 -b 1000 -p 20 -c 20 -i 10000 | grep c: | wc -l
The goal of this problem is to solve the producer/consumer problem using semaphores. You will use the pthread library to create producer threads and consumer threads. Each producer thread inserts a single ‘X’ character into a buffer. Each consumer thread removes the most recently inserted ‘X’ from the buffer. Each thread then repeats the process. Use POSIX semaphore (sem_init
, sem_wait
, sem_post
, sem_destroy
). The pseudocode is given in Pseudocode 1 below. Please stick to the main structure in the pseudocode, but feel free to add more parameters, variables, and functions as they become necessary.
Pseudocode for producer/consumer problem using semaphores:
# define N 10000000
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;
void main( void )
{
// create -p # of producer threads
// create -c # of consumer threads
}
void * producer( void )
{
while(1)
{
sem_wait(&empty);
sem_wait(&mutex);
// insert X into the first available slot in the buffer
insert('X');
sem_post(&mutex);
sem_post(&full);
}
}
void * consumer( void )
{
while(1)
{
sem_wait(&full);
sem_wait(&mutex);
// remove X from the last used slot in the buffer
remove();
sem_post(&mutex);
sem_post(&empty);
}
}
The goal of this problem is to create your own monitor to provide synchronization support for the producer/consumer problem. You will use the pthread library again to create producer threads and consumer threads. Each producer thread inserts a randomly generated character from the alphabet (upper and lower cases) into the first available slot in a buffer. Each consumer thread removes a character from the most recently used slot of the buffer. Each thread then repeats the process. Pseudocode 2 provides the basic outline your code should take, but feel free to add more parameters, variables, and functions as they become necessary.
Pseudocode for producer/consumer problem using monitors:
// ===== pro_con.c ===== //
void main( void )
{
// any functions you think necessary
// create producer threads
// create consumer threads
// join all threads
}
void * producer( ) // add more parameters as needed
{
char alpha;
while(1) {
alpha = generate_random_alphabet();
mon_insert(alpha);
}
}
void * consumer( ) //add more parameters as needed
{
char result;
while(1) {
result = mon_remove();
}
}
// ===== monitor.c ===== //
#define N 10000000
typedef struct {
// condition variable fields
} cond;
int count(cond cv)
{
// return # of threads blocked on cv
}
void wait(cond cv)
{
// give up exclusive access to monitor
// and suspend appropriate thread
// implement either Hoare or Mesa paradigm
}
void signal(cond cv)
{
// unblock suspended thread at head of queue
// implement either Hoare or Mesa paradigm
}
cond empty, full;
int count = 0;
char buf[N];
// add more variables as necessary
// define condition variable struct
// define monitor struct
void mon_insert(char alpha)
{
// implement either Hoare or Mesa paradigm
// synchronization and bookkeeping
while(count == N)
wait(full);
insert_item(alpha);
count = count+1;
signal(empty);
}
char mon_remove()
{
// implement either Hoare or Mesa paradigm
// synchronization and bookkeeping
char result;
while(count == 0)
wait(empty);
result = remove_item();
count = count-1;
signal(full);
return result;
}
struct
) consists of an integer variable that indicates the number of threads blocked on a condition variable and a semaphore that is used to suspend threads. There are three operations that can be performed on the CV. They are:
count(cv)
—returns the number of threads blocked on the cv
.wait(cv)
— relinquishes exclusive access to the monitor and then suspends the executing threads.signal(cv)
—unblocks one thread suspended at the head of the cv blocking queue. The signaled thread resumes execution where it was last suspended.pthread_cond_init
, or any of the other pthread_
functions) You should be able to complete the assignment with:
sem_wait()
- decrement (lock) a semaphoresem_post()
- increment (unlock) a sempahoresem_init()
- initialize a semaphoresem_destroy()
- destroy the semaphorepthread_
function calls you will lose points.mon_insert
that inserts a character into the buffer. If the buffer is full, it invokes wait on the condition variable full. It also invokes signal on condition variable empty.mon_remove
that removes a character from the buffer. If the buffer is empty, it invokes wait on the condition variable empty. It also invokes signal on condition variable full. The function returns the removed character.