CS69201 Week9
CS69201 Week9
CS69201 Week9
Important Instructions
1. Using Linux
1. This assignment is based on OS programming and will be beneficial for the students to do this assignment
in a Linux OS.
2. If you don’t have Linux, use Virtualbox + Linux image, both free. We suggest Ubuntu but feel free to
use any Linux version. If you decide to use Cygwin or WSL, it’s up to you; just remember that most of
your batchmates (and the instructors) will use Linux (and Ubuntu), so, naturally, we might not be able to
provide support for other systems.
3. In case of any difficulties in understanding any functionalities, you may use the same functionality in a
Linux Terminal to get a better understanding of how it works.
2. Programming Language
This assignment will use the C++ programming language.
4. Deliverables
Submit a zip file named <your roll no Assignment9.zip> which should include:
• All .cpp files [3 files for the 3 parts] <your roll no A9 part{i}.cpp> where i ∈ {1, 2, 3}
• Reports for all the 3 parts <your roll no part{i} report.txt> where i ∈ {1, 2, 3}
• Separate makefile: Resource for each part
• An additional readme file (call it README) containing details about how to compile the files and any
additional information required for the instructor during checking that you feel is necessary.
1
Objective
You already know about threads in operating systems and possibly have implemented basic threads in your
undergraduate curriculum. However, the basic implementation might not always suffice in terms of throughput.
For example, if a thread has a dedicated handler function to perform (which is the case in the basic version
of pthreads), then you cannot use that thread for any other functions and have to create a thread for each handler
function—this is expensive.
So, you might want to create a thread pool which is always waiting for tasks and can perform functions
(called tasks) as and when they come, complete it and wait for the next task. Even this might be a problem due
to the blocking nature of your approach. If some of these tasks have waits (e.g., to do I/O or for sleeping), then
a thread does not make progress until the system call is done / sleep is over. This is called blocking behaviour.
So, for high-performance versions of threads for the real world, we need to implement non-blocking vari-
ants. In this assignment, you will implement and compare three threading approaches in C++: basic Pthreads,
blocking std::threads, and a non-blocking solution using C++20 coroutines and lock-free programming.
To facilitate this comparison and provide a foundation for your implementation, you might want to check
the asyncio library available at https://github.com/netcan/asyncio. This library offers a frame-
work for building efficient asynchronous I/O operations in C++, which is similar for the non-blocking portion
of your assignment.
Tasks
Task 1: Basic Thread Creation with Pthreads library [25 marks]
• Mathematical Operations: [2 marks]
– Take five basic mathematical operations: addition, subtraction, multiplication, division, and expo-
nentiation (power) using one function for each.
1 int add(int x, int y) { return x + y; }
2 int subtract(int x, int y) { return x - y; }
3 int multiply(int x, int y) { return x * y; }
4 double divide(double x, double y) { return x / y; }
5 int power(int x, int y) { return std::pow(x, y); }
– Each function will take input values, perform the operation, and return the result.
– Create a struct to hold the input parameters and results for each mathematical operation.
– Implement wrapper functions that take a pointer to the struct, perform the operation, and store the
result in the appropriate field of the struct.
– These wrapper functions will serve as the entry point/handler functions for each thread.
– Use the pthread library to create a separate thread for each mathematical operation. Pass the appro-
priate struct (with input values) to each thread.
– Ensure that the main thread waits for each thread to complete by using pthread join().
2
– After joining the threads, print the input values and results for each operation to verify correctness.
– Ensure that the results from the threaded operations are accurate and displayed in an organized
manner.
– Implement functions that perform basic tasks such as addition, subtraction, and multiplication.
Introduce artificial delays in each operation to simulate long-running computations (e.g., sleeping
the thread for a few seconds).
– Introduce sleep to ensure that each operation takes a comparative large amount of time to complete
– e.g., 3–7 seconds.
– Create threads using the C++ standard library (std::thread) for each time-consuming operation.
– Launch each thread with the necessary arguments for the respective tasks (see section 1 above for
the structure description).
– Ensure that after launching the threads, you use appropriate mechanisms to wait for their comple-
tion, such as joining each thread before moving on to the next one.
– Start a timer before creating the threads to measure how long it takes for all operations to complete.
– After launching the threads, use blocking techniques (like join()) to ensure that the main thread
waits for each task to finish sequentially.
– Once all threads have been completed, record and display the total time taken, demonstrating that
tasks are executed in a blocking, sequential manner.
– Introduce exception handling to account for any potential issues that may arise during thread exe-
cution. Use try-catch blocks to ensure that exceptions are properly handled.
3
– Ensure that even in the case of an exception, all threads are properly joined, and no resources are
left unmanaged.
– Implement basic mathematical operations (e.g., addition, subtraction, multiplication, division, ex-
ponentiation) in an asynchronous, non-blocking fashion using C++20 coroutines.
– Each function should return a result asynchronously, allowing for multiple operations to be per-
formed concurrently.
– You are free to choose how you structure and manage the coroutines and task system (i.e., the
structure, input, and output format).
– Implement a non-blocking job queue that can hold tasks (above mathematical functions) and dis-
tribute them across multiple threads.
– The queue should make use of atomic operations to handle enqueueing and dequeueing without
locks. (Ensure the queue is thread-safe without using mutexes or locks.)
4
• Coroutine Task System: [5 marks]
– Create a process (task system) to manage and schedule coroutines, allowing for the execution of
multiple asynchronous tasks.
– Design a mechanism in the process for managing coroutine states, such as checking when a task is
ready and retrieving its result. (Consider using system calls like poll and select.)
– Implement a process (assigner) that efficiently assigns jobs to the thread pool and manages their
execution concurrently.
– Ensure that job submission and result processing are non-blocking and utilize atomic operations or
lock-free techniques.
– Demonstrate your system by submitting a set of concurrent jobs (e.g., 100 or more) using the
asynchronous mathematical functions.
– A test script to run the performance comparison.
– Write a report in a text file (<your roll no A9> part3 report.txt) stating:
* Compare the performance (in terms of latency) of your non-blocking implementation with a
traditional blocking version that executes tasks sequentially (modify the std::thread implemen-
tation for the baseline).
* How did you structure and manage the coroutines and task system (i.e., the structure, input and
output format)?
* What lock-free techniques did you use?
* How the thread pool and task scheduling are structured (data structure, design, etc.)?
* What is the design of your non-blocking job queue?
* Any challenges faced and how did you overcome them?