I. Background
In some CPU-intensive application scenarios, the processing of computational tasks is time-consuming (such as image processing), considering the advantages of multi-core CPU, if the calculation can be shared into multiple threads can be effectively utilized CPU;
However, if too many threads are turned on, the overhead of thread creation and destruction, and switching between threads, can not be underestimated; Ii. related Knowledge
2.1 Ideas Finishing
For this scenario, the design thread pool handles the task, that is, all pending tasks are concentrated in the queue, and N threads take turns to compute the queue;
2.2 Implementation of queues
Queue using a previous article to achieve the "chain queue", the queue data for the task of the callback function, task parameters;
The interface used is as follows:
Queue Request: Queue_alloc
Row operation: Queue_push
Column operation: Queue_pop
Queue destruction: Queue_free
2.2 Thread-related interface mutex initialization: Pthread_mutex_init (pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
Lock: Pthread_mutex_lock (pthread_mutex_t *mutex);
Unlock: Pthread_mutex_unlock (pthread_mutex_t *mutex);
Conditional variable initialization: pthread_cond_init (pthread_cond_t *cond,const pthread_cond_t *attr);
Thread hangs wait: pthread_cond_wait (pthread_cond_t *cond,pthread_mutex_t *mutex);
Wake single: Pthread_cond_signal (pthread_cond_t *cond);
All wakes: Pthread_cond_broadcast (pthread_cond_t *cond);
Third, realize
The definition of the structure, struct tpool is the management structure of the thread pool, struct routine for the task unit to be executed:
#define MAX_THREAD_NUM
#define MAX_TASKITEM 1024
typedef struct TPOOL
{
U8 enable;
queue_t *queue;
pthread_attr_t attr;
pthread_mutex_t Mutex;
pthread_cond_t cond;
pthread_t Tids[max_thread_num];
U16 num;
} tpool_t;
struct routine {
void *args;
void (*callback) (void *);
Initialization of the thread pool, pre-boot max_thread_num child threads, each child thread ready to wait:
tpool_t *tpool_alloc (U16 num) {int ret = failure;
int IX = 0;
tpool_t *phead = NULL;
if (num = = 0 | | | num > Max_thread_num) {goto _e1;
} Phead = calloc (1, sizeof (tpool_t));
if (!phead) {goto _e1;
} phead->enable = 1;
Phead->num = num;
Phead->queue = Queue_alloc (Max_taskitem);
if (!phead->queue) {goto _e2;
ret = Pthread_attr_init (&phead->attr);
RET |= pthread_mutex_init (&phead->mutex, NULL);
RET |= pthread_cond_init (&phead->cond, NULL);
if (SUCCESS!= ret) {goto _e3;
ret = Pthread_attr_setdetachstate (&phead->attr, pthread_create_detached);
if (SUCCESS!= ret) {goto _e4; for (ix = 0; IX < num; ix++) {ret = Pthread_create (&phead->tids[ix], NULL, __worker, Phead
);
if (SUCCESS!= ret) {goto _e4; } REt = SUCCESS;
Goto _e1;
_e4:pthread_mutex_destroy (&phead->mutex);
Pthread_cond_destroy (&phead->cond);
Pthread_attr_destroy (&PHEAD->ATTR);
_e3:queue_free (&phead->queue, free);
_e2:free_pointer (Phead);
_e1:return Phead; }
The implementation of the child thread is as follows, one is suspend hibernate when the queue is empty, wake up to take the unit of work in the queue to call, until the queue empty again into hibernation;
Because queues are shared resources, it is necessary to use locks for protection under multithreading;
static int __worker_routine (tpool_t *phead) {struct routine *prt = NULL;
Pthread_mutex_lock (&phead->mutex);
if (Queue_isempty (phead->queue)) {printf ("Thread #%u Go sleep!\n", (U32) pthread_self ());
Pthread_cond_wait (&phead->cond, &phead->mutex);
printf ("Thread #%u wakeup!\n", (U32) pthread_self ());
} prt = (struct routine *) Queue_pop (phead->queue);
Pthread_mutex_unlock (&phead->mutex);
if (PRT) {prt->callback (Prt->args);
return SUCCESS;
return failure;
} static void *__worker (void *args) {tpool_t *phead = (tpool_t *) args;
if (!args) {return NULL; while (phead->enable) {if (SUCCESS!= __worker_routine (phead)) {printf ("__worker_r
Outine return, Thread quit!\n ");
Break
} return NULL; }
The above working child thread is the consumer, also needs the main thread to act as the producer, puts the work thread to the work task;
int Tpool_routine_add (tpool_t *phead, Void (*callback) (void *), void *args) {struct routine *prt
= NULL;
if (!phead | |!callback | |!args) {return
failure;
}
PRT = (struct routine *) calloc (1, sizeof (struct routine));
if (!prt) {return
failure;
}
Prt->callback = callback;
Prt->args = args;
Pthread_mutex_lock (&phead->mutex);
if (SUCCESS!= queue_push (Phead->queue, NULL, PRT)) {
free_pointer (PRT);
Pthread_mutex_unlock (&phead->mutex);
return failure;
}
Pthread_cond_signal (&phead->cond);
Pthread_mutex_unlock (&phead->mutex);
return SUCCESS;
}
The destruction of the thread pool, which is to make the threads empty, then wait for all the child threads to exit, and then destroy the relevant members;
Note that the function is likely to block;
int tpool_destory (tpool_t *phead)
{
int ix = 0;
if (!phead) {return
failure;
}
phead->enable = 0;
Pthread_mutex_lock (&phead->mutex);
Pthread_cond_broadcast (&phead->cond);
Pthread_mutex_unlock (&phead->mutex);
for (ix = 0; IX < phead->num; ix++) {
pthread_join (Phead->tids[ix], NULL);
Pthread_mutex_destroy (&phead->mutex);
Pthread_cond_destroy (&phead->cond);
Pthread_attr_destroy (&phead->attr);
return SUCCESS;
}
Test function:
The working unit is the following, using hibernation 10 seconds to simulate time-consuming operations:
struct item {int value;};
void Test_worker (void *args) {struct Item *pitem = (struct item *) args;
if (!args) {printf ("null\n");
Return
printf ("Begin,%d\n", pitem->value);
Sleep (10);
printf ("End,%d\n", pitem->value);
Free (pitem); }
int main () {int ret = failure;
struct Item *pitem = NULL;
tpool_t *phead = NULL;
Assert_fail (NULL, phead = Tpool_alloc (10));
Sleep (2);
printf ("1\n");
Assert_fail (NULL, pitem = (struct item *) calloc (1, sizeof (struct item));
Pitem->value = 1;
ASSERT (SUCCESS, ret = Tpool_routine_add (Phead, Test_worker, Pitem));
printf ("2\n");
Assert_fail (NULL, pitem = (struct item *) calloc (1, sizeof (struct item));
Pitem->value = 2;
ASSERT (SUCCESS, ret = Tpool_routine_add (Phead, Test_worker, Pitem));
printf ("3\n");
Assert_fail (NULL, pitem = (struct item *) calloc (1, sizeof (struct item));
Pitem->value = 3;
ASSERT (SUCCESS, ret = Tpool_routine_add (Phead, Test_worker, Pitem));
Sleep (2);
printf ("close\n");
ASSERT (SUCCESS, ret = tpool_destory (phead)); _e1:printf ("Result:%s\n", ret?)
"Failure": "SUCCESS"); return ret?
exit_failure:exit_success; }
Iv. Summary
The test results are as follows:
The result indicates that when the work child thread is doing task processing (SLEEP10 simulation), pushing the new work task does not interrupt the current task, but is awakened by other idle threads;
At the same time when the exit signal is issued, because the program to wait for the current task after the end of the exit, so there is a certain delay;
Reference articles:
[1] C language to implement a simple thread pool, http://www.cnblogs.com/newth/archive/2012/05/09/2492459.html
[2] Mutual exclusion lock and condition variable, http://www.cnblogs.com/zendu/p/4981480.html