A channel is a model for synchronization via message passing. Messages may be sent over a channel by aMarketing Research and Data Analysis
thread (Sender) and other threads which have a reference to this channel can receive them (Receivers).
A channel can have multiple senders and receivers referencing it at any point of time.
Channels are used as a primitive to implement various other concurrent programming constructs. For
example, channels are heavily used in Google’s Go programming language and are very useful
frameworks for high-level concurrent programming. In this lab you will be writing your own version of a
channel which will be used to communicate among multiple clients. A client can either write onto the
channel or read from it. Keep in mind that multiple clients can read and write simultaneously from the
channel. You are encouraged to explore the design space creatively and implement a channel that is
correct and not exceptionally slow or inefficient. Performance is not the main concern in this project
(functionality is the main concern), but your implementation should avoid inefficient designs that sleep
for any fixed time or unnecessarily waste CPU time.
There are multiple variations to channels, such as whether the send/receive is blocking or non-blocking.
In blocking mode, receivers always block until there is data to receive, whereas in non-blocking mode,
they simply return. Similarly, with senders, in blocking mode, if the buffer is full, senders wait until some
receiver has retrieved a value and there is available space in the buffer whereas in non-blocking mode,
they simply leave without sending. In this lab, you will support both blocking and non-blocking
send/receive functions.
Another variation to channels would be if a channel is buffered (i.e., channel buffer size > 0) or
unbuffered (i.e., channel buffer size = 0). In the buffered case, the sender blocks only until the value has
been copied to the buffer. On the other hand, if the channel is unbuffered, the sender blocks until the
receiver has received the value. In this lab, you will only be responsible for supporting buffered
channels. Supporting unbuffered channels is extra credit and is especially difficult when implementing
select. The amount of extra credit is really small for the amount and difficulty of work involved, and
correctly implementing the unbuffered version for select is probably 2-3 times the work of the entire
buffered project.
The only files you will be modifying are channel.c and channel.h and optionally linked_list.c and
linked_list.h. You should NOT make any changes in any file besides these four files. You will be
implementing the following functions, which are described in channel.c and channel.h:
• channel_t* channel_create(size_t size)
• enum channel_status channel_send(channel_t* channel, void* data)
• enum channel_status channel_receive(channel_t* channel, void** data)
• enum channel_status channel_non_blocking_send(channel_t* channel, void* data)
• enum channel_status channel_non_blocking_receive(channel_t* channel, void** data)
• enum channel_status channel_close(channel_t* channel)
• enum channel_status channel_destroy(channel_t* channel)
• enum channel_status channel_select(select_t* channel_list, size_t channel_count, size_t*
selected_index)
The enum channel_status is a named enumeration type that is defined in channel.h. Rather than using
an int, which can be any number, enumerations are integers that should match one of the defined
values. For example, if you want to return that the function succeeded, you would just return SUCCESS.
You are encouraged to define other (static) helper functions, structures, etc. to help structure the code
in a better way.
Support routines
The buffer.c and buffer.h files contain the helper constructs for you to create and manage a buffered
channel. These functions will help you separate the buffer management from the concurrency issues in
your channel code. Please note that these functions are NOT thread-safe. You are welcome to use any
of these functions, but you should not change them.
• buffer_t* buffer_create(size_t capacity)
Creates a buffer with the given capacity.
• enum buffer_status buffer_add(buffer_t* buffer, void* data)
Adds the value into the buffer. Returns BUFFER_SUCCESS if the buffer is not full and value was
added. Returns BUFFER_ERROR otherwise.
• enum buffer_status buffer_remove(buffer_t* buffer, void** data)
Removes the value from the buffer in FIFO order and stores it in data. Returns BUFFER_SUCCESS
if the buffer is not empty and a value was removed. Returns BUFFER_ERROR otherwise.
• void buffer_free(buffer_t* buffer)
Frees the memory allocated to the buffer.
• size_t buffer_capacity(buffer_t* buffer)
Returns the total capacity of the buffer.
• size_t buffer_current_size(buffer_t* buffer)
Returns the current number of elements in the buffer.
We have also provided the optional interface for a linked list in linked_list.c and linked_list.h. You are
welcome to implement and use this interface in your code, but you are not required to implement it if
you don’t want to use it. It is primarily provided to help you structure your code in a clean fashion if you
want to use linked lists in your code. Linked lists may NOT be needed depending on your design, so do
not try to force it into your solution. You can add/change/remove any of the functions in linked_list.c
and linked_list.h as you see fit.
You are only allowed to use the pthread library, the POSIX semaphore library, basic standard C library
functions (e.g., malloc/free), and the provided code