本文给出基于纯c的一个线程池的实现。
设计思路
其实线程池是在多线程的基础上开发的,原理也很简单,就是需要在处理任务之前把线程启动好。如果只写成一个main.c到也容易实现。线程池可以用线程句柄的数组来组织,事先启动好。需要一个任务队列,主线程向任务队列里面push任务,线程池中的线程从任务队列里面取任务。本质都是围绕一个任务队列展开。
如果在一个main.c里面实现,任务队列写成全局的即可。这样对于主线程和工作线程都很容易获取。
如果要封装的话,采用将线程数组和任务队列封装到一起的做法。此时,工作线程由于无法获得队列,所以,需要将任务队列的地址传进去。但是,由于任务队列已经被封装进线程池。所以,从逻辑上来说,后者是一个整体。传入,线程池的地址即可。当然,传入队列的地址也是可以的。
难点
线程池的关闭是一个难点,在队列为空的条件下。工作线程都阻塞在条件变量上。(这是一个很重要的认识,因为pthread_cond_wait阻塞线程之后会把锁释放,这样别的工作线程一样可以拿锁,从而全部阻塞在条件变量上)。关闭的时候,先关闭队列标志,然后唤醒全部阻塞在条件变量上的线程。他们判断此时队列的标志,会跳出while(1)的循环,从而结束。这样,pthread_join就可以回收资源。
代码
- common.h
#ifndef COMMON_H_
#define COMMON_H_
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef struct task {
int left;
int right;
}task_t;
#endif
- threadpool.h
#ifndef THREADPOOL_H_
#define THREADPOOL_H_
#include <pthread.h>
#include "common.h"
#include "threadpool_queue.h"
typedef void* (*handle_t)(void*);
typedef struct threadpool {
pthread_t* pool_arr;
int pool_num;
handle_t pool_handle;
que_t pool_que;
}threadpool_t;
void threadpool_init( threadpool_t* ppool, int thread_num, handle_t thread_handle, int que_size );
void threadpool_destroy( threadpool_t* ppool );
void threadpool_start( threadpool_t* ppool );
void threadpool_stop( threadpool_t* ppool );
void threadpool_put( threadpool_t* ppool, task_t* val );
void threadpool_get( threadpool_t* ppool, task_t* val );
#endif
- threadpool.c
#include "threadpool.h"
#include "threadpool_queue.h"
#include <assert.h>
#include <stdlib.h>
void threadpool_init( threadpool_t* ppool,
int thread_num,
handle_t thread_handle,
int que_size) {
assert( ppool && thread_num>0 && thread_handle && que_size > 0 );
ppool->pool_arr = (pthread_t*)malloc( sizeof(pthread_t) * thread_num );
assert( ppool->pool_arr );
ppool->pool_num = thread_num;
ppool->pool_handle = thread_handle;
que_init( &(ppool->pool_que), que_size );
}// threadpool_init
void threadpool_destroy( threadpool_t* ppool ) {
assert( ppool );
free(ppool->pool_arr);
que_destroy( &(ppool->pool_que) );
}// threadpool_destroy
void threadpool_start( threadpool_t* ppool ) {
int i = 0;
assert( ppool );
que_start( &(ppool->pool_que) );
for( i = 0; i < ppool->pool_num; ++i ){
if( pthread_create( ppool->pool_arr + i, NULL, ppool->pool_handle, (void*)ppool ) != 0 ) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
}
}// threadpool_start
void threadpool_stop( threadpool_t* ppool ) {
int i = 0;
assert( ppool );
que_stop( &(ppool->pool_que) );
que_wake_all( &(ppool->pool_que) );
for( i = 0; i < ppool->pool_num; ++i ) {
if( pthread_join( ppool->pool_arr[i], NULL ) != 0 ) {
perror("pthread_join");
exit(EXIT_FAILURE);
}
}
}// threadpool_stop
void threadpool_put( threadpool_t* ppool, task_t* val ) {
assert( ppool );
que_push( &(ppool->pool_que), val );
}// threadpool_put
void threadpool_get( threadpool_t* ppool, task_t* val ) {
assert( ppool );
que_pop( &(ppool->pool_que), val );
}// threadpool_get
- threadpool_queue.h
这一部分做了较大的修改,主要是增加了队列的有效标志。以及两个start和stop函数,用来配合线程池里面的start,stop函数。wake_all函数也是用来配合线程池里面的stop函数。
#ifndef THREADPOOL_QUEUE_H_
#define THREADPOOL_QUEUE_H_
#include <pthread.h>
#include "common.h"
typedef struct que {
task_t* que_arr;
int que_front;
int que_rear;
int que_size;
pthread_mutex_t que_mtx;
pthread_cond_t que_cond_pro; // que full block
pthread_cond_t que_cond_con; // que empty block
int que_flag;
}que_t;
void que_init( que_t* pque, int size );
void que_destroy( que_t* pque );
void que_push( que_t* pque, task_t* val );
void que_pop( que_t* pque, task_t* val );
Status que_empty( que_t* pque ); // return true iff que is empty
Status que_full( que_t* pque ); // return true iff que is full
void que_start( que_t* pque );
void que_stop( que_t* pque );
void que_wake_all( que_t* pque );
Status que_valid( que_t* pque );
#endif
- threadpool_queue.c
#include "threadpool_queue.h"
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
void que_init( que_t* pque, int size ) {
assert(pque && size > 0);
pque->que_arr = (task_t*)malloc( size * sizeof(que_t) );
assert(pque->que_arr);
pque->que_size = size;
pque->que_front = pque->que_rear = 0;
if( ( pthread_mutex_init( &(pque->que_mtx) , NULL ) ) != 0 ){
perror("pthread_mutex_init");
exit(EXIT_FAILURE);
}
if( ( pthread_cond_init( &(pque->que_cond_pro), NULL ) ) != 0 ){
perror("pthread_cond_init");
exit(EXIT_FAILURE);
}
if( ( pthread_cond_init( &(pque->que_cond_con), NULL ) ) != 0 ){
perror("pthread_cond_init");
exit(EXIT_FAILURE);
}
pque->que_flag = 0;
}// que_init
void que_destroy( que_t* pque ) {
assert(pque);
assert(pque->que_arr);
free(pque->que_arr);
if( ( pthread_mutex_destroy( &(pque->que_mtx) ) ) != 0 ) {
perror("pthread_mutex_destroy");
exit(EXIT_FAILURE);
}
if( ( pthread_cond_destroy( &(pque->que_cond_pro) ) ) != 0 ) {
perror("pthread_mutex_destroy");
exit(EXIT_FAILURE);
}
if( ( pthread_cond_destroy( &(pque->que_cond_con) ) ) != 0 ) {
perror("pthread_mutex_destroy");
exit(EXIT_FAILURE);
}
}// que_destroy
void que_push( que_t* pque, task_t* val ) {
assert(pque && val);
pthread_mutex_lock( &(pque->que_mtx) );
while( que_full(pque) && que_valid(pque) ) {
pthread_cond_wait( &(pque->que_cond_pro), &(pque->que_mtx) );
}
if( que_valid(pque) ) {
pque->que_arr[pque->que_rear] = *val;
pque->que_rear = (pque->que_rear + 1) % pque->que_size;
pthread_cond_signal(&(pque->que_cond_con));
}
pthread_mutex_unlock( &(pque->que_mtx) );
}// que_push
void que_pop( que_t* pque, task_t* val ) {
assert(pque && val );
pthread_mutex_lock( &(pque->que_mtx) );
while( que_empty(pque) && que_valid(pque) ) {
pthread_cond_wait( &(pque->que_cond_con), &(pque->que_mtx) );
}
if( que_valid(pque) ) {
*val = pque->que_arr[pque->que_front];
pque->que_front = (pque->que_front + 1) % pque->que_size;
pthread_cond_signal(&(pque->que_cond_pro));
}
pthread_mutex_unlock( &(pque->que_mtx) );
}// que_pop
Status que_empty( que_t* pque ) {
assert(pque);
return pque->que_front == pque->que_rear;
}// que_empty
Status que_full( que_t* pque ) {
assert(pque);
return (pque->que_rear + 1) % pque->que_size == pque->que_front;
}// que_full
void que_start( que_t* pque ) {
assert(pque);
pque->que_flag = 1;
}// que_start
void que_stop( que_t* pque ) {
assert(pque);
pque->que_flag = 0;
}// que_stop
void que_wake_all( que_t* pque ) {
assert( pque );
pthread_cond_broadcast( &(pque->que_cond_con) );
pthread_cond_broadcast( &(pque->que_cond_pro) );
}// que_wake_all
Status que_valid( que_t* pque ) {
assert(pque);
return pque->que_flag;
}// que_valid
- main.c
最后的主函数,用select监听键盘输入。任务就是计算两个数的和。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <assert.h>
#include "threadpool.h"
#define LEN 128
static void* worker(void*);
static int process_task( task_t* val );
int main( int argc, char* argv[] ){
fd_set readfds;
fd_set tmpfds;
struct timeval tv;
int ret = 0;
int nread = 0;
char buf[LEN];
if( argc != 3 ){
fprintf( stderr, "Usage: argv[1] is thread num, argv[2] is que size!\n" );
exit(EXIT_FAILURE);
}
threadpool_t pool;
threadpool_init( &pool, atoi(argv[1]), worker, atoi(argv[2]) );
threadpool_start( &pool );
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
while(1) {
tmpfds = readfds;
tv.tv_sec = 3;
tv.tv_usec = 0;
ret = select( STDIN_FILENO+1, &tmpfds, NULL, NULL, &tv );
if( -1 == ret ) {
perror( "select" );
exit( EXIT_FAILURE );
}
else if( !ret ) {
printf( "No input data in 3 seconds!\n" );
}
else {
if( FD_ISSET(STDIN_FILENO, &tmpfds) ) {
memset(buf, 0, sizeof(buf));
nread = read( STDIN_FILENO, buf, LEN-1 );
if( -1 == nread ) {
perror( "read" );
exit( EXIT_FAILURE );
}
if( !nread ) break;
else{
buf[nread-1] = 0;
task_t val;
sscanf(buf, "%d %d", &val.left, &val.right);
threadpool_put( &pool, &val );
}
}
}
}
threadpool_stop( &pool );
threadpool_destroy( &pool );
exit(EXIT_SUCCESS);
}// main
static void* worker(void* arg) {
assert(arg);
threadpool_t* ppool = (threadpool_t*)(arg);
task_t val;
while( 1 ){
threadpool_get( ppool, &val );
if( !que_valid( &(ppool->pool_que) ) ) break;
int res = process_task(&val);
printf( "%lu execute the task: %d\n", pthread_self(), res );
sleep(1);
}
pthread_exit(NULL);
}// thread_handle
static int process_task( task_t* val ) {
return val->left + val->right;
}// process_task