在嵌入式系統內容下,由於系統資源和任務的特點,多線程成了實現多任務處理的重要方式.在一些常見的應用環境中,如Web伺服器,Email伺服器以及資料庫伺服器等都具有一個共同點:單位時間內必須處理很多並發的串連請求,但處理時間卻相對較短.傳統多線程方案中我們採用的伺服器模型則是一旦接受到請求之後,即建立一個新的線程,由該線程執行任務.任務執行完畢後,線程退出,這就是是"即時建立,即時銷毀"的策略.儘管與建立進程相比,建立線程的時間已經大大的縮短,但是如果提交給線程的任務是執行時間較短,而且執行次數及其頻繁,那麼伺服器將處於不停的建立線程,銷毀線程的狀態.
線程池是採用多線程解決方案來提高系統效能的一個最為出色的模型,它通過預建立一定數量的背景工作執行緒來對系統進行並發處理,使得系統的運行效率提高.因為線程池能使得系統採用較為輕量的,可控的系統資源實現系統並發處理能力的最大化,所以許多應用軟體都採用了線程池模型.
除此之外,線程是能夠限制建立的進程個數.通常線程池所允許的並發線程具有上界的,如果同時需要並發的線程數超過上界,那麼一部分線程將等待.而傳統方案中,如果同時請求資料為200,那麼最壞情況下,系統可能需要產生200個線程.儘管這不是一個很大的數目,但是也有系統達不到這個要求.
線程池模型有許多種,常見的有三種:任務隊列控制的線程池模型,背景工作執行緒控制的線程池模型,主控線程式控制制的線程池模型.本文將給出一中任務隊列控制的線程池模型的具體實現.
任務隊列控制的線程池模型是通過任務隊列來對線程池進行並發調度,如所示.線程池是由預建立的一個任務隊列和一組背景工作執行緒組成,其中任務隊列中存放工作對象.線程池啟動後,背景工作執行緒將採用輪詢的方式從任務隊列中擷取任務對象,由於初始化時任務隊列中不存在任務對象,這時的訊號量為0,所有的背景工作執行緒都處於阻塞狀態.主控線程將任務對象放入任務隊列中,並將訊號量加1,這樣訊號量就會喚醒一個阻塞中的背景工作執行緒(作業系統層面決定喚醒哪個阻塞的背景工作執行緒).背景工作執行緒喚醒後從任務隊列中擷取一個任務對象並執行該任務,執行完後,背景工作執行緒將再次訪問訊號量,如果訊號訊號量大於0,那麼背景工作執行緒將繼續從任務隊列中擷取任務對象並執行,知道訊號量等於0,這時的背景工作執行緒將再次被阻塞.
任務隊列控制的執行緒模式主要是通過任務隊列上的訊號來控制線程池中的線程調度.
本常式共由5個檔案構成:tpool.h,tpool.c,log.c,log.h,testpool.c.其中tpool.h,tpool.c是實現線程池的核心檔案,在tpool.h中定義了線程池和背景工作執行緒的資料結構及建立線程池和添加背景工作執行緒等方法,tpool.c中為具體的實現代碼.log.c,log.h提供了一種記錄檔案的產生手段,能在檔案中記錄自訂的資訊.testpool.c為線程池的測試程式.
該例子由多個源檔案組成,編譯命令如下:
下面給出這個例子的原始碼,首先是線程池的定義檔案tpool.h:
/*------------------------------------------------------------------------- * tpool.h – 線程池定義 * ------------------------------------------------------------------------- */#ifndef _TPOOL_H_#define _TPOOL_H_#include <stdio.h>#include <pthread.h>/*背景工作執行緒鏈表*/typedef struct tpool_work{ void (*handler_routine)();/*任務函數指標*/ void *arg;/*任務函數參數*/ struct tpool_work *next;/*下一個任務鏈表*/} tpool_work_t;/*線程池結構體*/typedef struct tpool{ int num_threads;/*最大線程數*/ int max_queue_size;/*最大任務鏈表數*/ int do_not_block_when_full;/*當鏈表滿時是否阻塞*/ pthread_t *threads;/*線程指標*/ int cur_queue_size; tpool_work_t *queue_head;/*鏈表頭*/ tpool_work_t *queue_tail;/*鏈表尾*/ pthread_mutex_t queue_lock;/*鏈表互斥量*/ pthread_cond_t queue_not_full;/*鏈表條件量-未滿*/ pthread_cond_t queue_not_empty;/*鏈表條件量-非空*/ pthread_cond_t queue_empty;/*鏈表條件量-空*/ int queue_closed; int shutdown;} tpool_t;/* 初始化串連池 */extern tpool_t *tpool_init(int num_worker_threads,\ int max_queue_size, int do_not_block_when_full);/* 添加一個背景工作執行緒 */extern int tpool_add_work(tpool_t *pool, void (*routine)(), void *arg);/* 清除線程池*/extern int tpool_destroy(tpool_t *pool, int finish);#endif /* _TPOOL_H_ */
/* ------------------------------------------------------------------------- * tpool.c – 線程池的實現 * ------------------------------------------------------------------------- */#include <stdio.h>#include <stdlib.h>#include <string.h> #include <pthread.h>#include "tpool.h"#include "log.h"/* 背景工作執行緒 */void *tpool_thread(void *tpool);/***************線程池初始化*****************************/tpool_t *tpool_init(int num_worker_threads,\/*線程池線程個數*/ int max_queue_size, \/*最大任務數*/ int do_not_block_when_full)/*是否阻塞任務滿的時候*/{ int i, rtn; tpool_t *pool; lprintf(log, INFO, "init pool begin ...\n"); /* 建立線程池結構體 */ if((pool = (struct tpool *)malloc(sizeof(struct tpool))) == NULL) { lprintf(log, FATAL, "Unable to malloc() thread pool!\n"); return NULL; } /* 設定線程池架構體成員 */ pool->num_threads = num_worker_threads; /*背景工作執行緒個數*/ pool->max_queue_size = max_queue_size; /*任務鏈表最大長度*/ pool->do_not_block_when_full = do_not_block_when_full; /*任務鏈表滿時是否等待*/ /* 產生線程池緩衝 */ if((pool->threads = (pthread_t *)malloc(sizeof(pthread_t)*num_worker_threads)) == NULL) { lprintf(log, FATAL,"Unable to malloc() thread info array\n"); return NULL; } /* 初始化任務鏈表 */ pool->cur_queue_size = 0; pool->queue_head = NULL; pool->queue_tail = NULL; pool->queue_closed = 0; pool->shutdown = 0; /* 初始化互斥變數,條件變數 用於線程之間的同步 */ if((rtn = pthread_mutex_init(&(pool->queue_lock),NULL)) != 0) { lprintf(log,FATAL,"pthread_mutex_init %s",strerror(rtn)); return NULL; } if((rtn = pthread_cond_init(&(pool->queue_not_empty),NULL)) != 0) { lprintf(log,FATAL,"pthread_cond_init %s",strerror(rtn)); return NULL; } if((rtn = pthread_cond_init(&(pool->queue_not_full),NULL)) != 0) { lprintf(log,FATAL,"pthread_cond_init %s",strerror(rtn)); return NULL; } if((rtn = pthread_cond_init(&(pool->queue_empty),NULL)) != 0) { lprintf(log,FATAL,"pthread_cond_init %s",strerror(rtn)); return NULL; } /* 建立所有的線程 */ for(i = 0; i != num_worker_threads; i++) { if( (rtn=pthread_create(&(pool->threads[i]),NULL,tpool_thread,(void*)pool)) != 0) { lprintf(log,FATAL,"pthread_create %s\n",strerror(rtn)); return NULL; } lprintf(log, INFO, "init pthread %d!\n",i); } lprintf(log, INFO, "init pool end!\n"); return pool;}函數tpool_add_work為線程池添加了一個背景工作執行緒.因為預建立的線程是不能做任何工作的,只有分配了適當的任務後,才會使預建立的線程真正的工作起來.
int tpool_add_work(tpool_t *pool, \ /*線程池指標*/ void (*routine)(void *),\ /*背景工作執行緒函數指標*/ void *arg) /*背景工作執行緒函數參數*/{ int rtn; tpool_work_t *workp; /*當前背景工作執行緒*/ if((rtn = pthread_mutex_lock(&pool->queue_lock)) != 0) { lprintf(log,FATAL,"pthread mutex lock failure\n"); return -1; } /* 採取獨佔的形式訪問任務鏈表 */ if((pool->cur_queue_size == pool->max_queue_size) && \ (pool->do_not_block_when_full)) { if((rtn = pthread_mutex_unlock(&pool->queue_lock)) != 0) { lprintf(log,FATAL,"pthread mutex lock failure\n"); return -1; } return -1; } /* 等待任務鏈表為新線程釋放空間 */ while((pool->cur_queue_size == pool->max_queue_size) && (!(pool->shutdown || pool->queue_closed))) { if((rtn = pthread_cond_wait(&(pool->queue_not_full), &(pool->queue_lock)) ) != 0) { lprintf(log,FATAL,"pthread cond wait failure\n"); return -1; } } if(pool->shutdown || pool->queue_closed) { if((rtn = pthread_mutex_unlock(&pool->queue_lock)) != 0) { lprintf(log,FATAL,"pthread mutex lock failure\n"); return -1; } return -1; } /* 分配背景工作執行緒結構體 */ if((workp = (tpool_work_t *)malloc(sizeof(tpool_work_t))) == NULL) { lprintf(log,FATAL,"unable to create work struct\n"); return -1; } workp->handler_routine = routine; workp->arg = arg; workp->next = NULL; if(pool->cur_queue_size == 0) { pool->queue_tail = pool->queue_head = workp; if((rtn = pthread_cond_broadcast(&(pool->queue_not_empty))) != 0) { lprintf(log,FATAL,"pthread broadcast error\n"); return -1; } } else { pool->queue_tail->next = workp; pool->queue_tail = workp; } pool->cur_queue_size++; /* 釋放對任務鏈表的獨佔 */ if((rtn = pthread_mutex_unlock(&pool->queue_lock)) != 0) { lprintf(log,FATAL,"pthread mutex lock failure\n"); return -1; } return 0;}當線程池退出後,需要釋放所用的資源,包括以下五個步驟:
int tpool_destroy(tpool_t *pool, int finish){ int i, rtn; tpool_work_t *cur; /*當前背景工作執行緒*/ lprintf(log, INFO, "destroy pool begin!\n"); /* 釋放對任務鏈表的獨佔 */ if((rtn = pthread_mutex_lock(&(pool->queue_lock))) != 0) { lprintf(log,FATAL,"pthread mutex lock failure\n"); return -1; } /* 第一步,設定線程退出標記 */ lprintf(log, INFO, "destroy pool begin 1!\n"); if(pool->queue_closed || pool->shutdown) { if((rtn = pthread_mutex_unlock(&(pool->queue_lock))) != 0) { lprintf(log,FATAL,"pthread mutex lock failure\n"); return -1; } return 0; } /* 第二步,禁止新任務加入任務鏈表 */ lprintf(log, INFO, "destroy pool begin 2!\n"); pool->queue_closed = 1; if(finish) { while(pool->cur_queue_size != 0) { if((rtn = pthread_cond_wait(&(pool->queue_empty),&(pool->queue_lock))) != 0) { lprintf(log,FATAL,"pthread_cond_wait %d\n",rtn); return -1; } } } /* 第三步,設定線程池銷毀標記 */ lprintf(log, INFO, "destroy pool begin 3!\n"); pool->shutdown = 1; if((rtn = pthread_mutex_unlock(&(pool->queue_lock))) != 0) { lprintf(log,FATAL,"pthread mutex unlock failure\n"); return -1; } /* 第四步,等待所有已建立的線程退出 */ lprintf(log, INFO, "destroy pool begin 4!\n"); if((rtn = pthread_cond_broadcast(&(pool->queue_not_empty))) != 0) { lprintf(log,FATAL,"pthread_cond_boradcast %d\n",rtn); return -1; } if((rtn = pthread_cond_broadcast(&(pool->queue_not_full))) != 0) { lprintf(log,FATAL,"pthread_cond_boradcast %d\n",rtn); return -1; } for(i = 0; i < pool->num_threads; i++) { if((rtn = pthread_join(pool->threads[i],NULL)) != 0) { lprintf(log,FATAL,"pthread_join %d\n",rtn); return -1; } } /* 第五步,釋放線程池所佔的記憶體空間 */ free(pool->threads); while(pool->queue_head != NULL) { cur = pool->queue_head->next; pool->queue_head = pool->queue_head->next; free(cur); } free(pool); lprintf(log, INFO, "destroy pool end!\n"); return 0;}函數tpool_thread定義了背景工作執行緒的函數,其中真正與實際任務有關的只有一行代碼:
(*(my_work->handler_routine))(my_work->arg);
即執行my_work->handler_routine指標指向的函數,並傳入參數my_work->arg.其他的步驟都是為執行這個任務而進行的各種設定和準備.
void *tpool_thread(void *tpool) { tpool_work_t *my_work; tpool_t *pool = (struct tpool *)tpool; for(;;) {/* 線程內迴圈 */ pthread_mutex_lock(&(pool->queue_lock)); /* 如果工作清單為0,並且線程池沒有關閉,則一直等待,直到任務到來為止 */ while((pool->cur_queue_size == 0) && (!pool->shutdown)) { pthread_cond_wait(&(pool->queue_not_empty), &(pool->queue_lock)); } /* 線程池是否已經關閉,如果線程池關閉則線程自己主動關閉 */ if(pool->shutdown) { pthread_mutex_unlock(&(pool->queue_lock)); pthread_exit(NULL); /*線程退出狀態為空白,主線程不捕獲各副線程狀態*/ } my_work = pool->queue_head; pool->cur_queue_size--; /*將任務鏈表頭部去掉,改任務正在處理中*/ if(pool->cur_queue_size == 0) pool->queue_head = pool->queue_tail = NULL; else pool->queue_head = my_work->next; /* 任務鏈表還沒有滿 */ if((!pool->do_not_block_when_full) &&\ (pool->cur_queue_size == (pool->max_queue_size - 1))) { pthread_cond_broadcast(&(pool->queue_not_full)); } /*任務鏈表為空白*/ if(pool->cur_queue_size == 0) { pthread_cond_signal(&(pool->queue_empty)); } pthread_mutex_unlock(&(pool->queue_lock)); /*啟動線程業務處理邏輯*/ (*(my_work->handler_routine))(my_work->arg); free(my_work); } return(NULL);}工作狀態的記錄為了便於記錄線程池的工作狀態,還實現了一個記錄伺服器.該記錄伺服器實現了一中類似syslog的日誌功能,總共就三個函數,使用十分簡單.log.h是該記錄伺服器的標頭檔.
/* ------------------------------------------------------------------------- * log.h 記錄函數定義 * ------------------------------------------------------------------------- */#ifndef __LOG_H#define __LOG_H#include <stdio.h>#include <semaphore.h>/*記錄的最大長度*/#define LOGLINE_MAX 1024/*記錄的等級*/#define DEBUG 1#define INFO 2#define WARN 3#define ERROR 4#define FATAL 5/*記錄的類型*/#define LOG_TRUNC 1<<0#define LOG_NODATE 1<<1#define LOG_NOLF 1<<2#define LOG_NOLVL 1<<3#define LOG_DEBUG 1<<4#define LOG_STDERR 1<<5#define LOG_NOTID 1<<6typedef struct { int fd; sem_t sem; int flags;} log_t;/* * 功能描述:記錄列印函數,將記錄列印至記錄檔案logfile。 * 參數:log_t - log_open()函數的傳回值* level - 可以是: DEBUG, INFO, WARN, ERROR, FATAL *fmt - 記錄的內容,格式同printf()函數* 傳回值:成功返回0,失敗返回-1*/int lprintf( log_t *log, unsigned int level, char *fmt, ... );/* * 功能描述: 初始化記錄檔案the logfile *參數:fname - 記錄檔案logfile的檔案名稱 * flags - 記錄格式的選項 * LOG_TRUNC -截斷開啟的記錄檔案 * LOG_NODATE -忽略記錄中的每一行 * LOG_NOLF -自動為每條記錄新開一行. * LOG_NOLVL - 不記錄訊息的等級 * LOG_STDERR -將訊息同時送到STDERR *傳回值:成功返回log_t(>0),失敗返回NULL*/log_t *log_open( char *fname, int flags );/* * 功能描述:關閉記錄檔案 * 參數:* log - 記錄檔案的指標*/void log_close( log_t *log );#endif
該記錄伺服器和線程池沒有直接的關係,如果有需要,可以很方便的該記錄伺服器移植到自己的項目中.
lprintf函數的功能是列印記錄,log指標指向了一個通過log_open函數開啟的記錄檔案,所有的記錄資訊都將儲存在這個檔案中.通過level參數指定記錄內容的分類,在log.h中定義了6種分類,分別是:DEBUG,INFO,WARN,ERROR和FATAL,便於對大量的記錄資訊進行分類.
/* ------------------------------------------------------------------------- * log.c – 記錄函數實現 * ------------------------------------------------------------------------- */#include <stdio.h>#include <unistd.h>#include <semaphore.h>#include <stdarg.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <time.h>#include <pthread.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include "log.h"int lprintf( log_t *log, unsigned int level, char *fmt, ... ) { int fd; int rc; va_list ap; time_t now; char date[50]; static char line[LOGLINE_MAX]; static char threadnum[10]; int cnt; static char *levels[6] = { "[(bad)] ", "[debug] ", "[info ] ", "[warn ] ", "[error] ", "[fatal] " }; if(!log) return -1; if( !(log->flags&LOG_DEBUG) && level == DEBUG ) return 0; fd=log->fd; /*日期*/ if( !(log->flags&LOG_NODATE) ) { now=time(NULL); strcpy(date,ctime(&now)); date[strlen(date)-6]=' '; date[strlen(date)-5]='\0'; } /*線程號*/ if( !(log->flags&LOG_NOTID) ) { sprintf(threadnum, "(TID:%lu) ", pthread_self()); } cnt = snprintf(line, sizeof(line), "%s%s%s", log->flags&LOG_NODATE ? "" : date, log->flags&LOG_NOLVL ? "" : (level > FATAL ? levels[0] : levels[level]), log->flags&LOG_NOTID ? "" : threadnum); va_start(ap, fmt); vsnprintf(line+cnt, sizeof(line)-cnt, fmt, ap); /*如果輸入的日誌過長會自動截取*/ va_end(ap); line[sizeof(line)-1] = '\0'; if( !(log->flags&LOG_NOLF) ) { /*chomp(line);*/ /*strcpy(line+strlen(line), "\n");*/ } sem_wait(&log->sem); /*用訊號實現同步*/ rc = write(fd, line, strlen(line)); if (log->flags&LOG_STDERR) write(2, line, strlen(line)); sem_post(&log->sem); if( !rc ) errno = 0; return rc;}
log_t *log_open( char *fname, int flags ) { log_t *log = malloc(sizeof(log_t)); if(!log) { fprintf(stderr, "log_open: Unable to malloc()"); goto log_open_a; } log->flags=flags; if( !strcmp(fname,"-") ) { log->fd = 2; } else { log->fd = open(fname, O_WRONLY|O_CREAT|O_NOCTTY | (flags&LOG_TRUNC ? O_TRUNC : O_APPEND) , 0666); } if( log->fd == -1 ) { fprintf(stderr, "log_open: Opening logfile %s: %s", fname, strerror(errno)); goto log_open_b; } if( sem_init(&log->sem, 0, 1) == -1 ) { fprintf(stderr, "log_open: Could not initialize log semaphore."); goto log_open_c; } return log;log_open_c: close(log->fd);log_open_b: free(log);log_open_a: return NULL;}log_close函數的作用是關閉一個開啟的記錄檔案.通常在函數退出的時候執行這個函數,以保證所有記錄資訊都正確的寫入記錄檔案.
void log_close( log_t *log ) { sem_wait(&log->sem); sem_destroy(&log->sem); close(log->fd); free(log); return;}
/* ------------------------------------------------------------------------- * testpool.c – 線程池測試程式 * ------------------------------------------------------------------------- */#include <pthread.h>#include "log.h"#include "tpool.h"log_t *log; /*進程全域記錄檔控制代碼*/*任務*/void thread(void *arg){ char * ptr=(char *)arg; sleep(1); printf("hello world! %s\n",ptr);}int main(int argc, char *argv[]){ tpool_t *pool; /*線程池指標*/ /* 開啟記錄檔案 */ log=log_open("test.log", 0); /* 建立一個有100個背景工作執行緒,最大200個任務隊列的線程池 */ pool=tpool_init(100,200,1); int i; /* 開啟記錄檔案 */ * 添加100個任務*/ for (i = 0; i<100;i++) tpool_add_work(pool,thread,"test!"); sleep(10); /*終止線程池*/ tpool_destroy(pool,1); /* 關閉記錄檔案 */ log_close(log); pthread_exit(NULL);}該例子示範了如何建立一個線程池,以及如何記錄程式的運行狀態,有一定的使用意義,稍加修改就可以應用到實際的項目中.能理解其中的設計思想和技巧,對自己編程能力的提高有很大的提高.