Thread pool
concept
Thread pool is to create some threads first, and their collection is called thread pool. The use of thread pool can improve the performance. The thread pool creates a large number of idle threads when the system starts. When there are tasks in the task pool, the threads in the thread pool queue up to pick up the tasks of the task pool. If there are no tasks at present, they will block the waiting.
Working mechanism
- In the case of wired process pool, the task is handed over to the thread pool rather than directly to a thread. After the thread pool gets the task, it looks for whether there is an idle thread, and if so, it will be delivered to the thread for execution.
- A thread can only execute one task at the same time. After execution, the thread returns to idle state and waits for the next task.
Reasons for using thread pools
Multithreading running time, the system constantly starts and closes threads, the cost is very high, it will consume thread resources excessively, and the risk of excessive thread switching, resulting in the collapse of system resources.
Graphical thread pool
code
Thread pool structure
typedef struct NMANAGER
{
struct NWORKER *workers; // Managed thread chain header node
struct NJOBS *jobs; // Task pool header node
int totWorkers; // How many threads are there currently
int idleWorkers; // Current number of idle threads
pthread_ mutex_ t pool_ mtx; // Thread pool mutex
pthread_ cond_ t pool_ cond; // Thread pool condition lock. All threads wait for this condition lock
} nManager;
Thread structure
typedef struct NWORKER
{
pthread_ t pthreadid; // The thread ID of the thread
struct NMANAGER *pool; // In which thread pool
int terminate; // Whether the thread is terminated or not, 1 is to destroy the thread, and 0 is the opposite
struct NWORKER *next; // Next thread in the linked list
struct NWORKER *prev;// Previous thread in the linked list
} nWorker;
Task pool
typedef struct NJOBS
{
void (*func)(void *arg); // Task callback, and the thread executes the task
void *user_ data; // Task parameters
struct NJOBS *next; // Next task in the task pool
struct NJOBS *prev; // Task pool previous task
} nJobs;
Creation of thread pool
int ThreadPoolCreate(threadpool *pool, int numsThread)
{
//1. Initialize thread pool related properties
//2. Create a certain number of threads and throw them into the thread pool
if (pool == NULL)
{
printf("create error\n");
return 1;
}
if (numsThread <= 0)
numsThread = 1;
memset(pool, 0, sizeof(nManager));
pool->totWorkers = 0;
pool->idleWorkers = 0;
pthread_mutex_init(&pool->pool_mtx, NULL);
pthread_cond_init(&pool->pool_cond, NULL);
//pool->pool_mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_t managerpid;
pthread_create(&managerpid, NULL, ManagerThread, (void *)pool);
pthread_detach(managerpid);
int i = 0;
for (i = 0; i < numsThread; i++)
{
nWorker *worker = (nWorker *)malloc(sizeof(nWorker));
if (worker == NULL)
{
printf("worker error\n");
return 1;
}
memset(worker, 0, sizeof(nWorker));
int ret = pthread_create(&worker->pthreadid, NULL, ThreadWorking, worker);
if (ret)
{
perror("pthread create");
return 1;
}
pthread_detach(worker->pthreadid);
worker->terminate = 0;
worker->pool = pool;
LIST_ ADD(worker, pool->workers); // Throw into thread pool
pthread_mutex_lock(&pool->pool_mtx);
pool->totWorkers++; // Total threads increased
pool->idleWorkers++; // Idle Thread
pthread_mutex_unlock(&pool->pool_mtx);
}
return 0;
}
Destroy thread pool
void ThreadPoolDestroy(threadpool *pool)
{
nWorker *w = pool->workers;
for (w = pool->workers; w != NULL; w = w->next)
{
w->terminate = 1; // If teminate = 1, the thread will destroy itself in the execution function, and the coupling degree is low.
}
pthread_mutex_lock(&pool->pool_mtx);
pthread_ cond_ broadcast(&pool->pool_cond); // Notify all threads waiting for conditional locks. Threads that do not wait will destroy themselves after completing tasks
pthread_mutex_unlock(&pool->pool_mtx);
free(pool);
}
Add task to thread pool
void ThreadPushJob(threadpool *pool, nJobs *job)
{
pthread_mutex_lock(&pool->pool_mtx);
LIST_ ADD(job, pool->jobs); // Add task to thread pool
printf("add jobs in pool\n");
pthread_ cond_ signal(&pool->pool_cond); // Notify a thread of the task
pthread_mutex_unlock(&pool->pool_mtx);
}
Thread execution function
void *ThreadWorking(void *arg)
{
nWorker *worker = (nWorker *)arg;
while (1)
{
pthread_mutex_lock(&worker->pool->pool_mtx);
while (worker->pool->jobs == NULL)
{
printf("work waiting------\n");
if (worker->terminate == 1)
{
//The thread was destroyed
break;
}
pthread_ cond_ wait(&worker->pool->pool_cond, &worker->pool->pool_mtx); // When there are tasks in the task pool,
//Unlock before the function, block on the condition variable, and add the corresponding mutex after the function
}
if (worker->terminate == 1)
{
pthread_mutex_unlock(&worker->pool->pool_mtx);
break;
}
nJobs *jobs = worker->pool->jobs;
LIST_ DEL(jobs, worker->pool->jobs); // Remove the task from the task pool
pthread_ mutex_ unlock(&worker->pool->pool_mtx); // Now allow other threads to fetch tasks from the task pool
pthread_mutex_lock(&worker->pool->pool_mtx);
worker->pool->idleWorkers--; // Idle thread minus one
pthread_mutex_unlock(&worker->pool->pool_mtx);
printf("working -------\n");
jobs->func(jobs->user_data); // Callback function to execute the task
pthread_mutex_lock(&worker->pool->pool_mtx);
worker->pool->idleWorkers++; // Idle threads plus one
pthread_mutex_unlock(&worker->pool->pool_mtx);
free(jobs); // Free up memory for this task
}
//Destroy the thread and modify the information related to the thread
pthread_mutex_lock(&worker->pool->pool_mtx);
worker->pool->totWorkers--;
worker->pool->idleWorkers--;
LIST_ DEL(worker, worker->pool->workers); // Remove from thread pool
pthread_mutex_unlock(&worker->pool->pool_mtx);
free(worker); // release
pthread_exit(NULL);
}
Remaining questions
- At present, thread pool does not support dynamic reduction and expansion. Generally, when there are too many idle threads, we consider reducing the thread pool capacity. When there are too many busy threads, we consider increasing threads.
- The structure of the thread pool already has a total number of threads and leisure threads, which can be used to write corresponding programs.
- For the size of thread pool, we can use a thread: manager thread to maintain the size of thread pool