Thread pool in C++
More threads access at the same data structure (a class named Item in this example) and the concurrency is managed by condition variable, lock (lock_guard and unique_lock) and mutex used in ThreadPool class.
In this way Item is a thread safe class and more threads (that will do some stuff) access to it.
- Boolean 'valid'
- The constructor of the class set this variable as true
- The destructor (or the method invalidate()) set this variable as false
- This is usefull to understand if the class is still valid and so if the threads that are waiting to do some stuff can terminate or not
- Mutex
- Mutual exclusion variable able to prevent race condition
- It is the requirement that one thread of execution never enter its critical section at the same time that another concurrent thread of execution enters its own critical section
- Condition variable
- It allows threads to have both mutual exclusion and the ability to wait (block) for a certain condition to become true
- Priority queue
- This is the true data that you access
- You can modify this data type and put another data type
public void push(int var)
- Method requires a var (of type int but you can modify it as you want) that have to be pushed in the queue
- A lock guard is defined
In this way a thread access to the critial section only when the lock is released by another one
lock_guard<mutex> lck(mtx);
- A check of valid is done
- The value is pushed on the priority queue
- Notify through condition variable that a value is inserted in the queue and so if there are at least one thread waiting for a value it can do its stuff
public bool pop(int &var)
- A unique lock is defined
Unlike lock guard the unique lock can be released when you want. Thanks to this if the queue is empty the thread can release the lock (in this way it allows other thread to acces to the data structure) and wait for a condition: i.e. a value is pushed in the priority queue
unique_lock<mutex> lck(mtx);
- Thread check if the queue is empty (and valid at the same time)
When the condition becomes true the thread will take the lock and go on
cv.wait(lck, [this](){return !(valid && pq.empty());});
- Pop the value from the top of the proprity queue
- 'var' will contain the poped variable and the method will return true
- If the method return false means that the class is not valid anymore
public void invalidate(void)
- A lock guard is defined
- Queue is invalidate
- Notify all thread of the changes
If threads are in
cv.notify_all();
cv.wait(...)
the will wake up and will see that the class is invalid and will terminate
- A type Item
- nthreads
- An int value that through thread::hardware_concurrency() will know the maximum number of concurrent threads that are supported
- Vector of threads
- Usefull to remember all generated threads
- Used also to terminate all threads
- Once flag
- It allows to call a function only once
public void startup(void)
- It have to be called and the start of the program
- This method have to be called only once otherwise an exception is raised
- It generated 'nthreads' and it associates to each thread the funciton worker()
threads.emplace_back(thread{&ThreadPool::worker, this});
public void shutdown(void)
- It have to be called and the end of the program
- This method have to be called only once otherwise an exception is raised
- It will invalidate the class and terminate all threads that are generated earlier
private void worker(void)
- While the queue is valid the method will go on
- A value is popped from the priority queue
- Given this value the method do some stuff (in this case only a cout)
public void post_task(int var)
- Method requires a var (of type int but you can modify it as you want) you want to push on the queue