Thread pool design idea
What is a thread pool
Let’s, for example, a thread pool is like a kit, every time we need screw is a screwdriver to removed from a toolbox, sometimes need to take a twist, sometimes more than the screw when need more people to take out the multiple to twist, twist his screw would screwdriver back again, and then the next time someone with out. Maybe my example isn’t perfect, but I think I’ve basically covered thread pools. Basically, a thread pool is a pool of resources, threads, that you take out of the pool when you need them to do something, and then you put them back in.
Why do we need thread pools
Let’s think about the question, why do we need thread pools? What would a thread look like every time we invoked it if there were no thread pool? Obviously, you create a thread first, then give the task to the thread, and then destroy the thread. If we use a thread pool instead, we would create a batch of threads while the program is running and hand them over to the thread pool to manage. Instead of creating and destroying threads every time, we take them out to handle tasks when we need them and recycle them back into the thread pool when we don’t. One could argue that you use a thread pool to consume some memory initially and then never release it, which is a bit wasteful. It’s kind of a space for time thing, we’re taking up a little bit more memory but it’s worth it compared to the amount of time that we value.
The concept of pooling is a very common concept of space for time. Besides thread pools, there are process pools, memory pools, and so on. In fact, they all have the same idea that I apply for a batch of resources, and then I take them at will, and I don’t have to put them back. There’s this idea of cloud computing, they’re all the same.
How to design thread pools
Now the hardcore knowledge will begin, please sit and hold on to the handrail ~
Without further ado, let’s see what our thread pool looks like!
Design ideas
We need a thread pool class, so what do we need in a thread pool class? We’ll take a look
- We need to hold the threads we have created, so we need a container for them
- We need a container to hold our tasks and put them into this container each time
- Because it is a multithreaded read task, it is essential that we need to lock, each read task needs to lock and unlock
- We need to know when to terminate, so we need a variable to terminate, right
- In order to avoid polling to determine whether the task container is empty or not, this is too inefficient, so we use conditional variables here
So here’s what a conditional variable is. Condition variables are a synchronization mechanism in concurrent programming. Condition variables allow a thread to block until a condition has occurred and then continue executing, releasing the lock that was previously acquired without affecting the ability of others to acquire the lock. So conditional variables are powerful and efficient. (Condition variables and locks will be covered in detail in my multi-threaded article, which is not the point here, so I won’t go into details.)
So what do we need to do in a thread pool?
- The action of adding a task to the thread pool, and the thread should be notified that it is ready to pick up the task and execute it
- A loop operation that is constantly waiting for data in the task container to execute, which is what needs to be done after initialization
- The operation that stops the above loop by changing the termination variable
Well, to this has been detailed to write the design ideas clearly, the next to see the specific implementation
Implementation of thread pools
Let’s take a look at how the thread pool class is implemented. The comments are already very detailed, so we don’t have to go into the code.
class CThreadMangerPool
{
public:
CThreadMangerPool(void) :is_runing(false) {};bool init(int threadnum);// Initialize the function
~CThreadMangerPool(void);
void Run(void); // Execute the function
void stop(void); // The function used to terminate the loop
void addTask(ThreadTask* task);// Add the task's function to the task container
private:
bool CreateThreads(int threadnum = 5);
std::vector<std::shared_ptr<std::thread>> threadsPool; // The thread container is used to store threads
std::list<std::shared_ptr<ThreadTask>> threadTaskList; // The task container is used to store tasks executed by the thread
std::condition_variable threadPool_cv; // Condition variable
std::mutex threadMutex; / / the mutex
//std::vector
<:shared_ptr>
> tcpClients;
bool is_runing; // Terminates the variable
};
Copy the code
Let’s implement ~ with a few key functions
In the Run function, we designed a loop that continuously executes the wait and pulls out the task to execute, sleeping the wait if no task can execute (using the condition variable mentioned earlier).
Notice the trick here, where we use while to determine whether the data in the task container is empty or not, because there is a false wake up of the condition variable, similar to the shock of the process. (I will not expand on it because it is not the key point here. I will explain it in detail in the multi-threading section of my article.)
void CThreadMangerPool::Run(a){
std::shared_ptr<ThreadTask> task;
while(true) {// in a loop
std::unique_lock<std::mutex> guard(threadMutex);// Use RALL to manage locks without manually releasing them
while(threadTaskList.empty()) {// False wake up of condition variables is prevented, so no if judgment is used
if(! is_runing)break;
threadPool_cv.wait(guard); // The use of conditional variables
}
if(! is_runing)// If stop is not started or is called, the loop will exit
break;
task = threadTaskList.front(a);// Retrieve the task
threadTaskList.pop_front(a);// Remove the task from the container
if (task == NULL)
continue;
task->DoIt(a);// Execute the task handler function
task.reset(a);// Reset the pointer}}Copy the code
Now let’s see how the increment function is implemented. Okay
void CThreadMangerPool::addTask(ThreadTask* task){
std::shared_ptr<ThreadTask> ptr; // Create a smart pointer to the task
ptr.reset(task);
{
std::lock_guard<std::mutex> guard(threadMutex); // Use RALL to manage locks without manual release
threadTaskList.push_back(ptr); // Add tasks to the tasks container
}
threadPool_cv.notify_all(a);// The notification thread is ready to execute, which is to wake up the condition that was sleeping at the condition variable
}
Copy the code
Ok, now that we’ve looked at the key functions, we can easily implement other functions including initialization functions, termination functions and so on
End scatter flower ~
This code comes from my back-end framework, Ratel, for those who are interested
Github address: github.com/hailong666/…
Past review:
Introduction to homemade Ratel backend framework
Daemon module design ideas
Design idea of log module
Configuration file module design