The bounded-buffer problems (aka the producer-consumer problem) is a classic example of concurrent access to a shared resource. A bounded buffer lets multiple producers and multiple consumers share a single buffer. Producers write data to the buffer and consumers read data from the buffer.
Among the slides you find self study material about classic synchronization problems. In these slides the bounded buffer problem is described. Before you continue, make sure to read the self study slides about the bounded buffer problem.
A bounded buffer with capacity N
has can store N
data items. The places used
to store the data items inside the bounded buffer are called slots. Without
proper synchronization the following errors may occur.
You will implement the bounded buffer as an abstract datatype. These are the files you will use to implement and test your implementation.
module-4/mandatory/src/bounded_buffer.h
.module-4/mandatory/src/bounded_buffer.c
file.module-4/mandatory/src/bounded_buffer_test.c
file. Here you can add your own
tests if you want.
module-4/mandatory/src/bounded_buffer_test.c
program make it easy to
test the implementaion for different sizes of the buffer and different numbers
of producers and cosumers.
The provided code has been developed and tested on the department Linux system and macOS. Most likely you will be able to use any resonable modern version of Linux.
You will us the psem semaphores to synchronize the bounded buffer.
To implement the bounded buffer you will use two C structures and an array of C structures.
The buffer will store tuples (pairs) with two integer
elements a
and b
. A tuple is represented by the following C struct.
typedef struct {
int a;
int b;
} tuple_t;
To implement the bounded buffer a finite size array in memory is shared by a number for producer and consumer threads.
In addition to the shared array, information about the state of the buffer must also be shared.
In the below example three data items B, C and D are currently in the buffer. On
the next write data will be written to index in = 4
. On the next read
data will be read from index out = 0
.
The buffer is represented by the following C struct.
typedef struct {
tuple_t *array;
int size;
int in;
int out;
psem_t *mutex;
psem_t *data;
psem_t *empty;
} buffer_t;
The purpose of the struct members are described below.
tuple_t
.All updates to the buffer state must be done in a critical section. More specifically, mutual exclusion must be enforced between the following critical sections:
in
.out
.A binary semaphore can be used to protect access to the critical sections.
Producers must block if the buffer is full. Consumers must block if the buffer is empty. Two counting semaphores can be used for this.
Use one semaphore named empty to count the empty slots in the buffer.
Use one semaphore named data to count the number of data items in the buffer.
A new bounded buffer with 10 elements will be represented as follows.
The empty
semaphore counts the number of
empty slots in the buffer and is initialized to 10
. Initially there are no
data element to be consumed in the buffer and the data
semaphore is
initialized to 0
. The mutex
semaphore will be used to enforece mutex when
updating the buffer and is initialized to 1
.
To complete the implementation you must add code at a few places.
Make sure you know how to use pointers to C strutcs before you continue.
You will complete the implementaion step by step.
For each step you will need to add code to a single function in the
module-4/mandatory/src/bounded_buffer.c
source file. For each step there
is also a test in the module-4/mandatory/src/bounded_buffer_test.c
source
file that should pass without any failed assertions.
In the terminal, navigate to the module-4/mandatory
directory. Use make to compile the program.
$> make
Run the test(s).
$> ./bin/bounded_buffer_test
An example of a failed test where the assertion that the buffer size should be 10 fails.
==== init_test ====
Assertion failed: (buffer.size == 10), function init_test, file src/bounded_buffer_test.c, line 24.
When a test passes you will see the following output.
Test SUCESSFULL :-)
You must complete the buffer initialization.
buffer_init
init_test
Make sure to initialize all members of the buffer_t
struct.
You must complete the buffer initialization.
buffer_destroy
destroy_test
Deallocate all resources allocated by buffer_init()
.
psem_destroy()
to deallocate all semaphores.NULL
after dealloacation to avoid dangling
pointers.
buffer_print
print_test
Add code to print all elements of the buffer array.
buffer_put
put_test
Here you need to:
buffer->in
index make sure it wraps around.buffer_get
get_test
Here you need to:
buffer->out
index make sure it wraps around.For this step you only need to run the concurrent_put_get_test
. If the test
doesn’t terinate you most likely have made a mistake with the synchroziation of
buffer_put
and/or buffer_get
that result in a deadlock.
It is now time to test the bounded buffer with multiple concurrent producers and
consumers. In module-4/mandatory/src/bounded_buffer_stress_test.c
you find a
complete test program that creates:
s
p
producer threads, each producing n
items into the bufferc
consumer threads, each consuming m
items from the bufferFor the test program to terminate, the total number of consumed items (c*m
)
must equal the total number of items produced (p*n
). Run the stress test.
$> ./bin/bounded_buffer_stress_test
The default stress test will now be exuted. On success you should see outpt similar to the following.
Test buffer of size 10 with:
20 producers, each producing 10000 items.
20 consumers, each consuming 10000 items.
Verbose: false
The buffer when the test ends.
---- Bounded Buffer ----
size: 10
in: 0
out: 0
array[0]: (7, 9994)
array[1]: (15, 9999)
array[2]: (13, 9998)
array[3]: (4, 9999)
array[4]: (7, 9995)
array[5]: (13, 9999)
array[6]: (7, 9996)
array[7]: (7, 9997)
array[8]: (7, 9998)
array[9]: (7, 9999)
---------------------draft: false
---
====> TEST SUCCESS <====
Note the ====> TEST SUCCESS <====
at the end.
The default values for test parameters can be overrided using the following flags.
Flag | Description | Argument |
---|---|---|
-s | Size of the bufffer | positive integer |
-p | Number of producer threads | positive integer |
-n | Number of items produced by each producer | positive integer |
-c | Number of consumer threads | positive integer |
-m | Number of items consumed by each consumer | positive integer |
-v | Turns on verbose ouput | no argument |
In the following example, the default buffer size 10 is changed to 3.
$> ./bin/bounded_buffer_stress_test -s 3
Exeriment with different values for the test parameters.
Here are a few examples of questions that you should be able to answer, discuss and relate to the source code of you solution during the code grading.
array
and the in
and out
array indexes.array
and the in
and out
array indexes.