$19.60
Learning Outcomes Implement a bounded buffer using threads and locks. Required Reading PoCSD 5.2 Instructions For this assignment you will be implementing a bounded buffer using threads and locks and testing it with multiple senders and receivers. The Bounded Buffer I have provided structures and prototypes for your bounded buffer in the file bbuff.h. You will need to implement the functions I have specified in a file called bbuff.c. There are two structures: • bb_msg is a structure that represents a simple message. It has two fields: t_id represents the id number of the thread that is sending the message and m_id represents the id number of the message itself. • bbuff is a structure that represents the buffer itself. It is based on the bounded buffer with locks from Section 5.2 of the PoCSD textbook. Note that it uses a predefined value BUFFER_SIZE to represent the number of messages that can be stored in the buffer. There are three functions having to do with messages: • bb_init_msg is used to initialize a message with a given thread id and message id. It is passed a pointer to a message that has already been allocated in memory. • bb_copy_msg is used to copy the contents of one message (the source) into another message (the destination). Both messages are passed as pointers and have already been allocated in memory. 1 • bb_display_msg is used to display the contents of a message along with the id of the receiving thread to standard output. This can be done using the printf function, which uses format codes to insert values into a string. For example: printf ( " num1 is % d and num2 is % d . " , num1 , num2 ); will display the following if num1 has a value of 5 and num2 has a value of 8: num1 is 5 and num2 is 8. There are three functions having to do the bounded buffer: • bb_init is used to initialize a bounded buffer. The in and out variables are initialized to zero. See the section below on POSIX Threads and Locks for how to initialize the lock. • bb_send is used to send a message to the buffer. It should do the following: 1. Acquire the buffer lock. 2. While the buffer is full, release the lock, then acquire it again so that a receiving thread can access the buffer. 3. Copy the message from the bb_msg structure provided to the correct location in the buffer using the bb_copy_msg function. 4. Increment the buffer’s in member variable. 5. Release the buffer lock. • bb_receive is used to receive a message from the buffer. It should do the following: 1. Acquire the buffer lock. 2. While the buffer is empty, release the lock, then acquire it again so that a sending thread can access the buffer. 3. Copy the message from the correct location in the buffer to the bb_msg structure provided using the bb_copy_msg function. 4. Increment the buffer’s out member variable. 5. Release the buffer lock. POSIX Threads and Locks The standard thread library for UNIX-based systems is POSIX Threads (aka Pthreads.) To use this library you will need the following include statement: #inc lude < pthread .h > To create and use a thread you must do the following: 1. Declare a variable of type pthread_t. 2. Write a function whose argument and return value are both of type void * (this means the function can take and return pointers to anything.) 3. Create a structure that can hold all of the information that the thread will need to execute, and create an instance of this structure containing the information for this thread. 2 4. Start the thread using the following function call: pthread_create (& my_thread , NULL , my_function , & args ); where my_thread is the thread variable from step 1, my_function is the name of the function from step 2, and args is the structure from step 3. 5. Wait for the thread to complete using the following function call: pthread_join ( my_thread , NULL ); The POSIX thread library also has support for locks that can guarantee mutually exclusive access to shared data. To create and use a lock you must do the following: 1. Create a variable of type pthread_mutex_t. 2. Initialize this variable using the following function call: pthread_mutex_init (& my_lock , NULL ); 3. When the currently running thread needs to acquire the lock, use the following function call: pthread_mutex_lock (& my_lock ); 4. When the currently running thread needs to release the lock, use the following function call: pthread_mutex_unlock (& my_lock ); Testing the Bounded Buffer In addition to the bounded buffer code above you must also write code to test your bounded buffer with multiple sending and receiving threads. This functionality should be implemented in a file called thread_tester.c. I have provided a structure and three prototypes to help you do this. The t_args structure is used to store all of the arguments that will be required for a sending or receiving thread to get started. It contains the following data members: • t_id is the id number for the thread. • num_msgs is the number of messages for the thread to send or receive. • buffer is a pointer to the buffer that the thread is to use in order to send or receive messages. A structure of type t_args can be initialized using the t_args_init function. This function sets all of the fields of the t_args structure using the given parameters. The send_msgs function is executed by a sending thread. It must do the following: 1. Cast the void pointer args to a pointer to a t_args structure using the following statement: s tru c t t_args * real_args = ( s tru c t t_args *) args ; 2. Declare a bb_msg structure. (Note that in C you have to include the keyword struct when declaring a structure.) 3 3. Send the number of messages specified in the t_args structure by repeatedly doing the following: (a) Initialize the bb_msg structure with the appropriate thread id and message id using bb_init_msg. (b) Send the message to the buffer in the t_args structure using bb_send. 4. Return NULL. The receive_msgs function is executed by a receiving thread. It must do the following: 1. Cast the void pointer args to a pointer to a t_args structure. 2. Declare a bb_msg structure. 3. Receive the number of messages specified in the t_args structure by repeatedly doing the following: (a) Receive the message from the buffer in the t_args structure using bb_receive. (b) Display the message along with the thread id from the t_args structure using bb_display_msg. 4. Return NULL Your main function should do the following: 1. Declare a pthread_t variable for each of the senders and the receivers. 2. Declare a bbuff structure and initialize it using bb_init. 3. Declare a t_args structure for each of the sending and receiving threads and initialize them using t_args_init. The number of messages sent or received by each thread is up to you, but the total number of messages sent should equal the total number of messages received. 4. Start each of the sending threads using pthread_create, the send_msgs functions, and the appropriate t_args structure. 5. Start each of the receiving threads using pthread_create, the receive_msgs function, and the appropriate t_args structure. 6. Wait for each of the sending and receiving threads to complete using pthread_join. Note that in order for the threads to run concurrently you must “create” all of the threads before you “join” any of the threads. Compiling and Running Your Code To compile this code you should use the gcc compiler on the Linux server like you did for Assignment 3, only this time you will need to include all of your .c files as command line arguments and explicitly tell the compiler to link with the POSIX thread library: gcc -lpthread -o thread_tester thread_tester.c bbuff.c 4 Here is an example of a run of the code using 3 senders and 4 receivers and a total of 24 messages: ./thread_tester [sending thread: 1, message 0, receiving thread: 0] [sending thread: 1, message 4, receiving thread: 0] [sending thread: 1, message 1, receiving thread: 1] [sending thread: 1, message 6, receiving thread: 1] [sending thread: 1, message 7, receiving thread: 1] [sending thread: 0, message 0, receiving thread: 1] [sending thread: 0, message 1, receiving thread: 1] [sending thread: 2, message 0, receiving thread: 1] [sending thread: 1, message 3, receiving thread: 3] [sending thread: 2, message 1, receiving thread: 3] [sending thread: 2, message 2, receiving thread: 3] [sending thread: 0, message 2, receiving thread: 3] [sending thread: 1, message 5, receiving thread: 0] [sending thread: 2, message 3, receiving thread: 0] [sending thread: 0, message 4, receiving thread: 0] [sending thread: 0, message 5, receiving thread: 0] [sending thread: 0, message 3, receiving thread: 3] [sending thread: 0, message 6, receiving thread: 3] [sending thread: 1, message 2, receiving thread: 2] [sending thread: 0, message 7, receiving thread: 2] [sending thread: 2, message 4, receiving thread: 2] [sending thread: 2, message 5, receiving thread: 2] [sending thread: 2, message 6, receiving thread: 2] [sending thread: 2, message 7, receiving thread: 2] What to Hand In Your source files should have comments at the top listing your name, CSCI 4100, Assignment 6, and a brief explanation of what the program does. Download the source files to your local machine, put them into a zip file then upload the zip file to D2L in the dropbox called Assignment 6. If you don’t have access to a zip archiver you can use the following commands to create a zip archive on the linux server: $ mkdir yourlastnameAssign6 $ mv thread_tester.c bbuff.c bbuff.h yourlastnameAssign6 $ zip -r yourlastnameAssign6.zip yourlastnameAssign6 This will create a zip file called yourlastnameAssign6.zip which you can download to your local machine. 5