Pull from upstream before solving this task.
Task: Thread Synchronization
In the upstream repository, you will find a multi-threaded program without any synchronization. Now your task is to add correct and proper synchronization and make the program run flawlessly.
If you don’t know where and how to start, look at the help section below.
This time, we provide a framework for a Hero Organization Simulator.
At the start of the day, heroes select a country to fly to. Every country has a maximum value of heroes it tolerates. Make sure not to exceed this limit by letting every hero enter one by one.
Afterward, they look up the villain “database” to select a villain to fight. Heroes which target an enemy that is already being fought are wasting precious time. Make sure looking up a villain to fight is done one by one as well.
Once a hero has acquired a target villain, the fight will begin, but be aware! Every villain needs a certain amount of heroes present at once to be defeated. Individual heroes attacking by themselves lead to defeat. Make sure to gather enough heroes before unleashing them upon the enemy.
After a hero has endured some glorious battles, a trip to the hospital is necessary. Make sure every injured hero waits for a spot at the hospital if it is full and no one leaves angrily, but also do not lock the entire hospital down just because one hero is resting.
- Identify all actors and resources in the programs.
- Implement the missing parts of the programs, fulfilling the above requirements.
- You need to ensure that the resources are acquired safely.
- Think about potential data races and deadlock scenarios.
- Use synchronization primitives correctly to make access to all shared resources thread-safe.
- All shared resources must be accessed by only one thread at a time!
- As with all other assignments, you can still lose points during the interviews if you cannot explain your solution satisfactorily or any cheating is detected.
- Use at least one semaphore or condition variable – You can also use both of them. Think where which one fits best.
- Notice the marked TODO fields and which files are not to be modified.
- Make sure to read the code as well as the function headers carefully.
Test your programs multiple times and with multiple different parameters to find possible threading errors. Threading errors tend to crash programs/produce incorrect output very irregularly and are sometimes hard to reproduce. Think about your locking structure and check that all shared variables are locked. Also, check if all the locks are acquired and released in the correct order.
This assignment uses
CMake as a build system. The essential build steps are as follows:
cd A2/ cmake . make
cd A2/ mkdir build && cd build cmake .. make
Notice that an executable titled
heroes has been created in the folder you called
make in. You can now start the program by executing the command
./heroes num_heroes num_enemies, e.g.
./heroes 1 1.
First of all, the idea of this course is that you should learn to look up technical concepts and implement practical tasks by yourself. However, here is some help in case you get lost:
Most important resource:
Manual pages for the
man pthreads man pthread_mutex_lock man pthread_cond_wait man < ... >
Note: If you get an error while trying to open the manual pages for pthread_mutex_lock, you can install the glibc-doc (
sudo apt-get install glibc-doc) package to fix the issue.
Some important concepts
Always look up the function you use in their man pages. They will tell you how each function behaves in much more detail.
- Semaphore Holds an integer value that can be increased and decreased by threads.
- A fixed amount of threads can enter.
- It puts the calling thread to sleep when it wants to decrease the value and has reached 0 or less.
- Sleeping threads are woken up when another thread increases the value.
- Datatype: sem_t
- Popular functions: sem_init, sem_destroy, sem_wait, sem_post
- See also https://linux.die.net/man/7/sem_overview
- Mutex Primitive that can be used to make sure only one thread accesses critical sections of code simultaneously.
- Only one thread can enter.
- It can be locked and unlocked.
- If a thread tries to lock an already locked mutex, it is put to sleep until the mutex is unlocked by the thread that currently has it locked.
- A mutex applies the same concept as a semaphore with the values 0 or 1.
- Datatype: pthread_mutex_t
- Popular functions: pthread_mutex_init, pthread_mutex_destroy, pthread_mutex_lock, pthread_mutex_unlock
- See also https://hpc-tutorials.llnl.gov/posix/
- Condition Variable Primitive that can be used to put threads to sleep and wake them up from other threads.
- A CV is ideal for many cases where one thread needs to wait for the result of some processing in another thread.
- They are always protected by an accompanying mutex.
- Datatype: pthread_cond_t
- Popular functions: pthread_cond_init, pthread_cond_destroy, pthread_cond_wait, pthread_cond_signal, pthread_cond_broadcast
- See also https://hpc-tutorials.llnl.gov/posix/