Thread Pooling in C++
For a network proxy assignment, I needed to create a thread pooling class, so that connections would not needlessly create/destroy threads all the time. I figured it would be an ultimately useful thing to package up on its own, so here it is!
This code is also available on my GitHub here: https://github.com/Juskelis/Thread-Pooling
First, we have the ThreadPool.h class definition, which defines the vector for all of the threads we'll deal with, as well as a deque to hold unprocessed tasks. The deque is locked up by a mutex, and there is a condition variable to flag when there's something to process, which is also locked up by its own mutex. You'll notice that the enqueue function takes in a void function with a void pointer parameter, to allow anything to be passed in. In the initial application, this was the connection information and associated socket. The void pointer will pose a slight issue in implementation. Ultimately, the execute function is what we are threading, this will come into play later.
The enqueue function is a little bit more involved, however. The problem is that we have a deque that takes in full void functions. The reason that it takes in full void functions is because to have parameters I would need to keep a second deque, which introduces potential synchronization issues. Instead of dealing with that, I just have consume the parameter within enqueue by creating a lambda expression for the call:
Now for the execute function, which does a few weird things. First, it uses that void pointer parameter as a reference to the class, essentially turning the static function into a instance function. If you're wondering why I'm doing this in such a roundabout way, the short answer is that I wanted this to be easily convertible to/from pthreads. You'll notice that in a lot of places I do this, such as when I implement any locking/unlocking system, I mark where the lock/unlock should go for everything. Anyway, once we convert the void pointer back to a reference, we immediately enter into an infinite loop, which is just there to process tasks/wait. We do that waiting using the condition variable. Once we get a notification, we grab the first task, remove it from the deque, and then execute it. Finally, we go back to the start and wait all over again. Here's the whole thing:
Creating and deleting threads goes just as you'd think it would:
Finally, here's an example of everything being used. Its a bit contrived, but aren't most examples? It just prints out a simple message and a unique number, just to make sure that everything is referencing correctly:
One thing to note is that the destructor actually short circuits threads, which is not ideal. Because of that, you need to add in a way to wait for everything to wrap up at the end. I just use a cin.ignore.