CSC5101 – Advanced Programming of Multicore Architectures

Parallel and Distributed Systems track

Threads and synchronizations

My first threads

Write, in C, an application that creates N threads with pthread_create, where N is the first argument of the application. Each thread must write to the terminal "[tid] Hello, World i!!!", where:

  • tid is the thread identifier returned by pthread_self,
  • i is a number incremented by the main for each thread (0 for the first thread, 1 for the second and so on).

The main function has now to print Main quitting at the end of the function. Launch several times your application with 3 threads. Why, sometime, the application write the main quitting before the three hello worlds?

Start your application with 20 threads several times. Why, often, the application writes less than 20 times hello world?

Add a call to pthread_exit at the end of main. Why, now, the application systematically write 20 times hello world when you start it with 20 threads?

Ensure now that the application writes the main quitting after all the hello worlds.

A shared counter

First, add a global variable called counter initialized to 0 to your application. Then, ensure that each thread increments this variable after printing its hello world. Finally, modify the application in order to print the value of counter before the pthread_exit of the main.

When you run your application with 100 threads, why the printed counter is often smaller than 100? Modify your application in order to ensure that the counter is equal to the number of threads at end of the application.

Modify your application in order to print "Main quitting" only when the counter is equal to the number of threads.

Producer/consumer

In this exercise, we want to implement a producer/consumer. In detail, the main thread, called the producer hereafter, has to read lines from the standard input and to push them in a queue. The other threads, called the consumers hereafter, have to take these lines from the queue in the FIFO (first in first out) order and to print them.

We want to implement the queue with a linked-list. The first node of the list is the oldest enqueued line (first in), while the last node of the linked list is the newest enqueued line (last in). We also want to avoid following the whole linked-list to enqueue a new node. For this reason, you have to keep both a pointer to the first and to the last node.

First, implement a unicast queue: only a single consumer has to take the messages from the queue. You have to ensure that a consumer thread sleeps with pthread_cond_wait when the queue is empty. Don't forget to free a node of the linked list when it is consumed.
You can download the Makefile here.

Atomic broadcast in shared memory (optional, 1/2)

Modify your application in order to implement a multicast queue: each consumer has to receive and to print each line (and in the FIFO order). In this question also, don't forget to free the nodes of the linked list.

Atomic broadcast in shared memory (optional, 2/2)

With the broadcast, now, you can try to ensure that each consumer can consume a node even if the previous ones in the list are not consumers, which means totally desynchronizing the consumers (in your initial version, probably, all the consumers consume a node and then they all continue with the next one).