Linux高性能服务器编程:进程池和线程池原理及应用(有图有代码有真相!!!)
一、問題引入
在前面編寫多進(jìn)程、多線程服務(wù)器時(shí)通過動(dòng)態(tài)創(chuàng)建子進(jìn)程和子線程來實(shí)現(xiàn)并發(fā)服務(wù)器,這樣做有以下缺點(diǎn):
1)動(dòng)態(tài)創(chuàng)建進(jìn)程、線程將會(huì)比較耗費(fèi)時(shí)間,將導(dǎo)致較慢的客戶響應(yīng)。
2)動(dòng)態(tài)創(chuàng)建的子進(jìn)程只為一個(gè)客戶服務(wù),將會(huì)產(chǎn)生大量的細(xì)微進(jìn)程或線程,進(jìn)程或線程之間的切換將耗費(fèi)CPU大量的時(shí)間。
3)動(dòng)態(tài)創(chuàng)建的子進(jìn)程是當(dāng)前進(jìn)程的完整映像,當(dāng)前進(jìn)程必須謹(jǐn)慎管理其分配的文件描述符和堆內(nèi)存等系統(tǒng)資源,否則子進(jìn)程可能復(fù)制這些資源,使系統(tǒng)可用資源急劇下降,
進(jìn)而影響服務(wù)器性能。
要解決這些問題就要引入進(jìn)程池、線程池。
二、進(jìn)程池、線程池
1、池的概念
由于服務(wù)器的硬件資源“充裕”,那么提高服務(wù)器性能的一個(gè)很直接的方法就是以空間換時(shí)間,即“浪費(fèi)”服務(wù)器的硬件資源,以換取其運(yùn)行效率。
這就是池的概念。池是一組資源的集合,這組資源在服務(wù)器啟動(dòng)之初就完全被創(chuàng)建并初始化,這稱為靜態(tài)資源分配。當(dāng)服務(wù)器進(jìn)入正是運(yùn)行階段
,即開始處理客戶請(qǐng)求的時(shí)候,如果它需要相關(guān)的資源,就可以直接從池中獲取,無需動(dòng)態(tài)分配。很顯然,直接從池中取得所需資源比動(dòng)態(tài)分配資
源的速度要快得多,因?yàn)榉峙湎到y(tǒng)資源的系統(tǒng)調(diào)用都是很耗時(shí)的。當(dāng)服務(wù)器處理完一個(gè)客戶連接后,可以把相關(guān)的資源放回池中,無需執(zhí)行系統(tǒng)調(diào)
用來釋放資源。從最終效果來看,池相當(dāng)于服務(wù)器管理系統(tǒng)資源的應(yīng)用設(shè)施,它避免了服務(wù)器對(duì)內(nèi)核的頻繁訪問。
池可以分為多種,常見的有內(nèi)存池、進(jìn)程池、線程池和連接池。
2、 進(jìn)程池、線程池
進(jìn)程池和線程池相似,所以這里我們以進(jìn)程池為例進(jìn)行介紹。如沒有特殊聲明,下面對(duì)進(jìn)程池的討論完全是用于線程池。進(jìn)程池是由服務(wù)器預(yù)先
創(chuàng)建的一組子進(jìn)程,這些子進(jìn)程的數(shù)目在3~10個(gè)之間(當(dāng)然這只是典型情況)。線程池中的線程數(shù)量應(yīng)該和CPU數(shù)量差不多。進(jìn)程池中的所有子進(jìn)程都運(yùn)行著
相同的代碼,并具有相同的屬性,比如優(yōu)先級(jí)、PGID等。當(dāng)有新的任務(wù)來到時(shí),主進(jìn)程將通過某種方式選擇進(jìn)程池中的某一個(gè)子進(jìn)程來為之服務(wù)。
相比于動(dòng)態(tài)創(chuàng)建子進(jìn)程,選擇一個(gè)已經(jīng)存在的子進(jìn)程的代價(jià)顯得小得多。至于主進(jìn)程選擇哪個(gè)子進(jìn)程來為新任務(wù)服務(wù),則有兩種方法:
1)主進(jìn)程使用某種算法來主動(dòng)選擇子進(jìn)程。最簡(jiǎn)單、最常用的算法是隨機(jī)算法和Round Robin(輪流算法),但更優(yōu)秀、更智能的算法使得任務(wù)在各個(gè)工作進(jìn)程中均勻的分配
,從而減輕服務(wù)器的整體壓力
2)主進(jìn)程和所有子進(jìn)程通過一個(gè)共享的工作隊(duì)列來同步,子進(jìn)程都睡眠在該工作隊(duì)列上。當(dāng)有新的任務(wù)到來時(shí),主進(jìn)程將任務(wù)添加到工作隊(duì)列中。這將喚醒正在等待任 ? ? ? 務(wù)的子進(jìn)程,不過只有一個(gè)子進(jìn)程將獲得新任務(wù)的“接管權(quán)”,它可以從工作隊(duì)列中取出任務(wù)并執(zhí)行之,而其他子進(jìn)程將繼續(xù)睡眠在工作隊(duì)列上。
當(dāng)選擇好子進(jìn)程后,主進(jìn)程還需要使用某種通知機(jī)制來告訴目標(biāo)子進(jìn)程有新任務(wù)需要處理,并傳遞必要的數(shù)據(jù)。最簡(jiǎn)單的方式是,在父進(jìn)程和子進(jìn)程之間預(yù)先建立好一
條管道,然后通過管道來實(shí)現(xiàn)所有的進(jìn)程間通信。在父線程和子線程之間傳遞數(shù)據(jù)就要簡(jiǎn)單得多,因?yàn)槲覀兛梢园堰@些數(shù)據(jù)定義為全局,那么它們本身就是被所有線程共享的。
三、進(jìn)程池、線程池的部分應(yīng)用
1、應(yīng)用進(jìn)程池處理多客戶
在使用進(jìn)程池處理多客戶任務(wù)時(shí),首先要考慮的一個(gè)問題是:監(jiān)聽?socket?和連接?socket?是否都由進(jìn)程統(tǒng)一管理這兩種?socket?。這可以一下介紹的并發(fā)模式解決。服務(wù)器主要有兩種并發(fā)編程模式:半同步?/?半異步模式和領(lǐng)導(dǎo)者?/?追隨者模式。
其次,在設(shè)計(jì)進(jìn)程池時(shí)還需要考慮:一個(gè)客戶連接上的所有任務(wù)是否始終由一個(gè)子進(jìn)程來處理。如果說客戶任務(wù)是無狀態(tài)的,那么我們可以考慮使用不同的子進(jìn)程來為該客戶的不同請(qǐng)求服務(wù)。但如果客戶是存在上下文關(guān)系的,則最好一直用同一個(gè)子進(jìn)程來為之服務(wù),否則實(shí)現(xiàn)起來比較麻煩,因?yàn)槲覀儾坏貌辉诟髯舆M(jìn)程之間傳遞上下文數(shù)據(jù)。?epoll?的?EPOLLONESHOT?事件能夠確保一個(gè)客戶連接在整個(gè)生命周期中僅被一個(gè)線程處理。
2、線程池主要用于:
1)、需要大量的線程來完成任務(wù),且完成任務(wù)的時(shí)間比較短。 WEB服務(wù)器完成網(wǎng)頁請(qǐng)求這樣的任務(wù),使用線程池技術(shù)是非常合適的。因?yàn)閱蝹€(gè)任務(wù)小,而任務(wù)數(shù)量巨大,你可以想象一個(gè)熱門網(wǎng)站的點(diǎn)擊次數(shù)。但對(duì)于長時(shí)間的任務(wù),比如一個(gè)Telnet連接請(qǐng)求,線程池的優(yōu)點(diǎn)就不明顯了。因?yàn)門elnet會(huì)話時(shí)間比線程的創(chuàng)建時(shí)間大多了。
2)、對(duì)性能要求苛刻的應(yīng)用,比如要求服務(wù)器迅速響應(yīng)客戶請(qǐng)求。
3)、接受突發(fā)性的大量請(qǐng)求,但不至于使服務(wù)器因此產(chǎn)生大量線程的應(yīng)用。突發(fā)性大量客戶請(qǐng)求,在沒有線程池情況下,將產(chǎn)生大量線程,雖然理論上大部分操作系統(tǒng)線程數(shù)目最大值不是問題,短時(shí)間內(nèi)產(chǎn)生大量線程可能使內(nèi)存到達(dá)極限,并出現(xiàn)"OutOfMemory"的錯(cuò)誤。
3、c模擬實(shí)現(xiàn)線程池
#include <pthread.h> #include <unistd.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <stdio.h>/* 要執(zhí)行的任務(wù)鏈表 */ typedef struct tpool_work {void* (*routine)(void*); /* 任務(wù)函數(shù) */void *arg; /* 傳入任務(wù)函數(shù)的參數(shù) */struct tpool_work *next; }tpool_work_t;typedef struct tpool {int shutdown; /* 線程池是否銷毀 */int max_thr_num; /* 最大線程數(shù) */pthread_t *thr_id; /* 線程ID數(shù)組 */tpool_work_t *queue_head; /* 線程鏈表 */pthread_mutex_t queue_lock; pthread_cond_t queue_ready; }tpool_t;//創(chuàng)建線程池 int tpool_create(int max_thr_num); //銷毀線程池 void tpool_destroy();int tpool_add_work(void*(*routine)(void*), void *arg);static tpool_t *tpool = NULL;/* 工作者線程函數(shù), 從任務(wù)鏈表中取出任務(wù)并執(zhí)行 */ static void* thread_routine(void *arg) {tpool_work_t *work;while(1) {// 如果線程池沒有被銷毀且沒有任務(wù)執(zhí)行,會(huì)處于阻塞狀態(tài),//pthread_cond_wait是一個(gè)原子操作,等待前會(huì)解鎖,喚醒后會(huì)加鎖 pthread_mutex_lock(&tpool->queue_lock);while(!tpool->queue_head && !tpool->shutdown) {printf ("thread %lu is waiting\n", pthread_self ());pthread_cond_wait(&tpool->queue_ready, &tpool->queue_lock);}//線程池要銷毀了if (tpool->shutdown) {pthread_mutex_unlock(&tpool->queue_lock);printf ("thread %lu will exit\n", pthread_self ());pthread_exit(NULL);}work = tpool->queue_head;tpool->queue_head = tpool->queue_head->next;pthread_mutex_unlock(&tpool->queue_lock);work->routine(work->arg);free(work);}return NULL; }// * @brief 創(chuàng)建線程池 // * @param max_thr_num 最大線程數(shù) // * @return 0: 成功 其他: 失int tpool_create(int max_thr_num){int i;tpool = calloc(1, sizeof(tpool_t));if (!tpool) {printf("%s: calloc failed\n", __FUNCTION__);exit(1);}/* 初始化 */tpool->max_thr_num = max_thr_num;tpool->shutdown = 0;tpool->queue_head = NULL;if (pthread_mutex_init(&tpool->queue_lock, NULL) !=0) {printf("%s: pthread_mutex_init failed, errno:%d, error:%s\n",__FUNCTION__, errno, strerror(errno));exit(1);}if (pthread_cond_init(&tpool->queue_ready, NULL) !=0 ) {printf("%s: pthread_cond_init failed, errno:%d, error:%s\n", __FUNCTION__, errno, strerror(errno));exit(1);}/* 創(chuàng)建工作者線程 */tpool->thr_id = calloc(max_thr_num, sizeof(pthread_t));if (!tpool->thr_id) {printf("%s: calloc failed\n", __FUNCTION__);exit(1);}for (i = 0; i < max_thr_num; ++i) {if (pthread_create(&tpool->thr_id[i], NULL, thread_routine, NULL) != 0){printf("%s:pthread_create failed, errno:%d, error:%s\n", __FUNCTION__, errno, strerror(errno));exit(1);}} return 0;}/* 銷毀線程池 */void tpool_destroy(){int i;tpool_work_t *member;if (tpool->shutdown) {return;}tpool->shutdown = 1;/* 通知所有正在等待的線程 */pthread_mutex_lock(&tpool->queue_lock);pthread_cond_broadcast(&tpool->queue_ready);pthread_mutex_unlock(&tpool->queue_lock);for (i = 0; i < tpool->max_thr_num; ++i) {pthread_join(tpool->thr_id[i], NULL);}free(tpool->thr_id);while(tpool->queue_head) {member = tpool->queue_head;tpool->queue_head = tpool->queue_head->next;free(member);}pthread_mutex_destroy(&tpool->queue_lock); pthread_cond_destroy(&tpool->queue_ready);free(tpool); }//* @brief 向線程池中添加任務(wù)//* @param routine 任務(wù)函數(shù)指針//* @param arg 任務(wù)函數(shù)參數(shù)//* @return 0: 成功 其他:失敗 int tpool_add_work(void*(*routine)(void*), void *arg){tpool_work_t *work, *member;if (!routine){printf("%s:Invalid argument\n", __FUNCTION__);return -1;}work = malloc(sizeof(tpool_work_t));if (!work) {printf("%s:malloc failed\n", __FUNCTION__);return -1;}work->routine = routine;work->arg = arg;work->next = NULL;pthread_mutex_lock(&tpool->queue_lock); member = tpool->queue_head;if (!member) {tpool->queue_head = work;} else {while(member->next) {member = member->next;}member->next = work;}/* 通知工作者線程,有新任務(wù)添加 */pthread_cond_signal(&tpool->queue_ready);pthread_mutex_unlock(&tpool->queue_lock);return 0; }//測(cè)試 void *func(void *arg){printf("thread %d\n", (int)arg);return NULL;}int main(int arg, char **argv){if (tpool_create(5) != 0) {printf("tpool_create failed\n");exit(1);}int i;for (i = 0; i < 12; ++i) {//給線程池分配任務(wù)tpool_add_work(func, (void*)i);}sleep(2); tpool_destroy();//銷毀線程池 return 0;}結(jié)果:
總結(jié)
以上是生活随笔為你收集整理的Linux高性能服务器编程:进程池和线程池原理及应用(有图有代码有真相!!!)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构:神奇的B树实现解析(有图有代码
- 下一篇: Linux字符设备驱动剖析