池的概念:
由於服務器的硬件資源“充裕”,那麼提高服務器性能的一個很直接的方法就是以空間換時間,即“浪費”服務器的硬件資源,以換取其運行效率。這就是池的概念。
池是一組資源的集合,這組資源在服務器啓動之初就完全被創建並初始化,這稱爲靜態資源分配。當服務器進入正是運行階段,即開始處理客戶請求的時候,如果它需要相關的資源,就可以直接從池中獲取,無需動態分配。很顯然,直接從池中取得所需資源比動態分配資源的速度要快得多,因爲分配系統資源的系統調用都是很耗時的。
當服務器處理完一個客戶連接後,可以把相關的資源放回池中,無需執行系統調用來釋放資源。從最終效果來看,池相當於服務器管理系統資源的應用設施,它避免了服務器對內核的頻繁訪問。
池可以分爲多種,常見的有內存池、進程池、線程池和連接池。
進程池和線程池概述:
進程池是由服務器預先創建的一組子進程,這些子進程的數目在 3~10 個之間(當然這只是典型情況)。線程池中的線程數量應該和 CPU 數量差不多。
進程池中的所有子進程都運行着相同的代碼,並具有相同的屬性,比如優先級、 PGID 等。
當有新的任務來到時,主進程將通過某種方式選擇進程池中的某一個子進程來爲之服務。相比於動態創建子進程,選擇一個已經存在的子進程的代價顯得小得多。
至於主進程選擇哪個子進程來爲新任務服務,則有兩種方法:
(1)主進程使用某種算法來主動選擇子進程。
最簡單、最常用的算法是隨機算法和 Round Robin (輪流算法)。
(2)主進程和所有子進程通過一個共享的工作隊列來同步,子進程都睡眠在該工作隊列上。當有新的任務到來時,主進程將任務添加到工作隊列中。這將喚醒正在等待任務的子進程,不過只有一個子進程將獲得新任務的“接管權”,它可以從工作隊列中取出任務並執行之,而其他子進程將繼續睡眠在工作隊列上。
當選擇好子進程後,主進程還需要使用某種通知機制來告訴目標子進程有新任務需要處理,並傳遞必要的數據。最簡單的方式是,在父進程和子進程之間預先建立好一條管道,然後通過管道來實現所有的進程間通信。在父線程和子線程之間傳遞數據就要簡單得多,因爲我們可以把這些數據定義爲全局,那麼它們本身就是被所有線程共享的。
綜上所述,進程池的一般模型如下所示:
處理多客戶
在使用進程池處理多客戶任務時,首先考慮的一個問題是:監聽socket和連接socket是否都由主進程來統一管理。併發模型,其中半同步/半反應堆模式是由主進程統一管理這兩種socket的。而高效的半同步/半異步和領導者/追隨者模式,則是由主進程管理所有監聽socket,而各個子進程分別管理屬於自己的連接socket的。對於前一種情況,主進程接受新的連接以得到連接socket,然後它需要將該socket傳遞給子進程(對於線程池而言,父線程將socket傳遞給子線程是很簡單的。因爲他們可以很容易地共享該socket。但對於進程池而言,必須通過管道傳輸)。後一種情況的靈活性更大一些,因爲子進程可以自己調用accept來接受新的連接,這樣該父進程就無須向子進程傳遞socket。而只需要簡單地通知一聲:“我檢測到新的連接,你來接受它。
常連接,即一個客戶的多次請求可以複用一個TCP連接。那麼,在設計進程池時還需要考慮:一個客戶連接上的所有任務是否始終由一個子進程來處理。如果說客戶任務是無狀態的,那麼我們可以考慮使用不同的進程爲該客戶不同請求服務。
但如果客戶任務是存在上下文關係的,則最好一直用同一個進程來爲之服務,否則實現起來比較麻煩,因爲我們不得不在各個子進程傳遞上下文數據,我們採用epoll的EPOLLONESHOT事件,這一事件能夠確保一個客戶連接在整個生命週期中僅被一個線程處理。
半同步/半異步進程池實現
綜合前面的討論,我們可以實現這個進程池,爲了避免在父、子進程之間傳遞文件描述符,我們將接受新連接的操作放到子進程中,很顯然,對於這種模式而言,一個客戶連接上的所有任務始終是由一個子進程來處理的。
1、包含的頭文件:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
2、process結構體實現:
//描述一個子進程的類,
//m_pid是目標子進程的PID,m_pipefd是父進程和子進程通信用的管道
class process
{
public:
process() : m_pid( -1 ){}
public:
pid_t m_pid;
int m_pipefd[2];
};
3、進程池類的實現
//將它定義爲模板類是爲了代碼複用
//其模板參數是處理邏輯任務的類
template< typename T >
class processpool
{
private:
//將構造函數定義爲私有,因此我們只能通過後面的create靜態函數來創建
//processpool實例
processpool( int listenfd, int process_number = 8 );
public:
//單例模式,以保證程序最多創建一個processpool實例,這是程序正確處理信號的必要條件
static processpool< T > *create( int listenfd, int process_number = 8 )
{
if( !m_instance )
{
m_instance = new processpool< T >( listenfd, process_number );
}
return m_instance;
}
~processpool()
{
delete [] m_sub_process;
}
//啓動進程池
void run();
private:
void setup_sig_pipe();
void run_parent();
void run_child();
private:
//進程允許的最大子進程數量
static const int MAX_PROCESS_NUMBER = 16;
//每個子進程最多能處理的客戶數量
static const int USER_PER_PROCESS = 65536;
//epoll最多能處理的事件數
static const int MAX_EVENT_NUMBER = 10000;
//進程池中的進程總數
int m_process_number;
//子進程在池中的序號,從0開始
int m_idx;
//每個進程都有一個epoll內核事件表,用m_epoolfd標識
int m_epollfd;
//監聽socket
int m_listenfd;
//子進程通過m_stop來決定是否停止運行
int m_stop;
//保存所有子進程的描述信息
process *m_sub_process;
//進程池靜態實例
static processpool< T > *m_instance;
};
4、細節實現如下:
template< typename T >
processpool< T > *processpool< T >::m_instance = NULL;
//用於處理信號的管道,以實現統一事件源,後面稱之爲信號管道
static int sig_pipefd[2];
static int setnonblocking( int fd )
{
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
static void addfd( int epollfd, int fd )
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
setnonblocking( fd );
}
//從epollfd標識的epoll內核事件表中刪除fd上的所有註冊事件
static void removefd( int epollfd, int fd )
{
epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 );
close( fd );
}
static void sig_handler( int sig )
{
int save_errno = errno;
int msg = sig;
send( sig_pipefd[1], ( char * )&msg, 1, 0 );
errno = save_errno;
}
static void addsig( int sig, void( handler )(int), bool restart = true )
{
struct sigaction sa;
memset( &sa, '\0', sizeof( sa ) );
sa.sa_handler = handler;
if( restart )
{
sa.sa_flags |= SA_RESTART;
}
sigfillset( &sa.sa_mask );
assert( sigaction( sig, &sa, NULL ) != -1 );
}
//進程池構造函數。
//參數listenfd是監聽socket,它必須在創建進程池之前被創建,否則
//子進程無法直接引用它,參數process_number指定進程池中子進程的數量。
template< typename T >
processpool< T >::processpool( int listenfd, int process_number )
: m_listenfd( listenfd ), m_process_number( process_number ), m_idx( -1 ), m_stop( false )
{
assert( ( process_number > 0 ) && ( process_number <= MAX_PROCESS_NUMBER ) );
m_sub_process = new process[ process_number ];
assert( m_sub_process );
//創建process_number個子進程,並建立他們和父進程之間的管道
for( int i = 0; i < process_number; ++i )
{
int ret = socketpair( PF_UNIX, SOCK_STREAM, 0, m_sub_process[i].m_pipefd );
assert( ret == 0 );
m_sub_process[i].m_pid = fork();
assert( m_sub_process[i].m_pid >= 0 );
if( m_sub_process[i].m_pid > 0 )
{
close( m_sub_process[i].m_pipefd[1] );
continue;
}
else
{
close( m_sub_process[i].m_pipefd[0] );
m_idx = i;
break;
}
}
}
//統一事件源
template< typename T >
void processpool< T >::setup_sig_pipe()
{
//創建epoll事件監聽表和信號管道
m_epollfd = epoll_create( 5 );
assert( m_epollfd != -1 );
int ret = socketpair( PF_UNIX, SOCK_STREAM, 0, sig_pipefd );
assert( ret != -1 );
setnonblocking( sig_pipefd[1] );
addfd( m_epollfd, sig_pipefd[0] );
//設置信號處理函數
addsig( SIGCHLD, sig_handler );
addsig( SIGTERM, sig_handler );
addsig( SIGINT, sig_handler );
addsig( SIGPIPE, SIG_IGN );
}
//父進程中m_idx值爲-1,子進程中m_idx值大於等於0,我們據此判斷下來
//要運行的是父進程代碼還是子進程代碼
template< typename T >
void processpool< T >::run()
{
if( m_idx != -1 )
{
run_child();
return;
}
run_parent();
}
template< typename T >
void processpool< T >::run_child()
{
setup_sig_pipe();
//每個子進程都通過其在進程池中的序號值m_idx找到與父進程通信的管道
int pipefd = m_sub_process[m_idx].m_pipefd[ 1 ];
//子進程需要監聽管道文件描述pipefd,因爲父進程將通過它來通知子進程
//accept新連接
addfd( m_epollfd, pipefd );
epoll_event events[ MAX_EVENT_NUMBER ];
T *users = new T [ USER_PER_PROCESS ];
assert( users );
int number = 0;
int ret = -1;
while( ! m_stop )
{
number = epoll_wait( m_epollfd, events, MAX_EVENT_NUMBER, -1 );
if ( ( number < 0 ) && ( errno != EINTR ) )
{
printf( "epoll failure\n" );
break;
}
for ( int i = 0; i < number; i++ )
{
int sockfd = events[i].data.fd;
if( ( sockfd == pipefd ) && ( events[i].events & EPOLLIN ) )
{
int client = 0;
//從父/子進程之間的管道讀取數據,並將結果保存在變量client中。
//如果讀取成功,則表示有新的客戶連接到來。
ret = recv( sockfd, ( char * )&client, sizeof( client ), 0 );
if( ( ( ret < 0 ) && ( errno != EAGAIN ) ) || ret == 0 )
{
continue;
}
else
{
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof( client_address );
int connfd = accept( m_listenfd, ( struct sockaddr * )&client_address, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
continue;
}
addfd( m_epollfd, connfd );
//模板T必須實現init方法,以初始化一個客戶連接
//我們直接使用connfd來索引邏輯處理對象
//T類型的對象,以提高程序效率
users[connfd].init( m_epollfd, connfd, client_address );
}
}
//下面處理子進程接收到的信號
else if( ( sockfd == sig_pipefd[0] ) && ( events[i].events & EPOLLIN ) )
{
int sig;
char signals[1024];
ret = recv( sig_pipefd[0], signals, sizeof( signals ), 0 );
if( ret <= 0 )
{
continue;
}
else
{
for( int i = 0; i < ret; ++i )
{
switch( signals[i] )
{
case SIGCHLD:
{
pid_t pid;
int stat;
while ( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 )
{
continue;
}
break;
}
case SIGTERM:
case SIGINT:
{
m_stop = true;
break;
}
default:
{
break;
}
}
}
}
}
//如果是其他可讀數據,那麼必然是客戶請求到來。
//調用邏輯對象的process方法處理之
else if( events[i].events & EPOLLIN )
{
users[sockfd].process();
}
else
{
continue;
}
}
}
delete [] users;
users = NULL;
close( pipefd );
//close( m_listenfd );
//我們將這句話註銷掉,以提醒讀者,應該有m_listenfd的創建者
//來關閉這個文件描述符,即所謂的“對象(比如一個文件描述符,又或者一
//堆內存)由那個函數創建,就應該由那個函數銷燬
close( m_epollfd );
}
template< typename T >
void processpool< T >::run_parent()
{
setup_sig_pipe();
//父進程監聽m_listenfd
addfd( m_epollfd, m_listenfd );
epoll_event events[ MAX_EVENT_NUMBER ];
int sub_process_counter = 0;
int new_conn = 1;
int number = 0;
int ret = -1;
while( ! m_stop )
{
number = epoll_wait( m_epollfd, events, MAX_EVENT_NUMBER, -1 );
if ( ( number < 0 ) && ( errno != EINTR ) )
{
printf( "epoll failure\n" );
break;
}
for ( int i = 0; i < number; i++ )
{
int sockfd = events[i].data.fd;
if( sockfd == m_listenfd )
{
//如果有新連接到來,就採用RR方式將其分配給一個子進程處理
int i = sub_process_counter;
do
{
if( m_sub_process[i].m_pid != -1 )
{
break;
}
i = (i + 1) % m_process_number;
}
while( i != sub_process_counter );
if( m_sub_process[i].m_pid == -1 )
{
m_stop = true;
break;
}
sub_process_counter = (i + 1) % m_process_number;
//send( m_sub_process[sub_process_counter++].m_pipefd[0], ( char* )&new_conn, sizeof( new_conn ), 0 );
send( m_sub_process[i].m_pipefd[0], ( char * )&new_conn, sizeof( new_conn ), 0 );
printf( "send request to child %d\n", i );
//sub_process_counter %= m_process_number;
}
//下面處理父進程接收到的信號
else if( ( sockfd == sig_pipefd[0] ) && ( events[i].events & EPOLLIN ) )
{
int sig;
char signals[1024];
ret = recv( sig_pipefd[0], signals, sizeof( signals ), 0 );
if( ret <= 0 )
{
continue;
}
else
{
for( int i = 0; i < ret; ++i )
{
//如果進程池中第i個子進程退出了,
//則主進程關閉通信管道,並設置相應的m_pid爲-1,以標記該子進程已退出
switch( signals[i] )
{
case SIGCHLD:
{
pid_t pid;
int stat;
while ( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 )
{
for( int i = 0; i < m_process_number; ++i )
{
if( m_sub_process[i].m_pid == pid )
{
printf( "child %d join\n", i );
close( m_sub_process[i].m_pipefd[0] );
m_sub_process[i].m_pid = -1;
}
}
}
//如果所有子進程都已經退出了,則父進程也退出
m_stop = true;
for( int i = 0; i < m_process_number; ++i )
{
if( m_sub_process[i].m_pid != -1 )
{
m_stop = false;
}
}
break;
}
case SIGTERM:
case SIGINT:
{
//如果父進程接收到終止信號,那麼就殺死所有子進程,並等待它們全部結束,當然,
//通知子進程結束更好的方法是向父/子進程之間的通信管道發送特殊數據
printf( "kill all the clild now\n" );
for( int i = 0; i < m_process_number; ++i )
{
int pid = m_sub_process[i].m_pid;
if( pid != -1 )
{
kill( pid, SIGTERM );
}
}
break;
}
default:
{
break;
}
}
}
}
}
else
{
continue;
}
}
}
//由創建者關閉這個文件描述符
//close( m_listenfd );
close( m_epollfd );
}
4、用進程池實現的簡單CGI服務器
利用進程池來重新實現一個併發的CGI服務器,代碼如下所示:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include "processpool.h" /*引用上一節介紹的進程池*/
/*用戶處理客戶CGI請求的類,它可以作爲processpoll類的模板類*/
class cgi_conn
{
public:
cgi_conn(){}
~cgi_conn(){}
/*初始化客戶連接,清空讀緩衝區*/
void init( int epollfd, int sockfd, const sockaddr_in& client_addr )
{
m_epollfd = epollfd;
m_sockfd = sockfd;
m_address = client_addr;
memset(m_buf, '\0', BUFFER_SIZE);
m_read_idx = 0;
}
void process()
{
int idx = 0;
int ret = -1;
/*循環讀取和分析客戶數據*/
while( true )
{
idx = m_read_idx;
ret = recv( m_sockfd, m_buf+idx, BUFFER_SIZE-1-idx, 0);
/*如果讀操作發生錯誤,則關閉客戶連接,但如果是暫時無數據可讀,則退出循環*/
if( ret < 0 )
{
if( errno != EAGAIN )
{
removefd( m_epollfd, m_sockfd );
}
break;
}
else if( ret == 0 )
{
removefd( m_epollfd, m_sockfd );
break;
}
else
{
m_read_idx += ret;
printf("user content is:%s\n", m_buf);
/*如果遇到字符"\r\n",則開始處理客戶請求*/
for(; idx<m_read_idx; ++idx)
{
if( (idx>=1) && (m_buf[idx-1] == '\r') && (m_buf[idx] == '\n') )
{
break;
}
}
/*如果沒有遇到字符“\r\n”,則需要讀取更多客戶數據*/
if( idx == m_read_idx)
{
continue;
}
m_buf[idx-1] = '\0';
char* file_name =m_buf;
/*判斷客戶要運行的CGI程序是否存在*/
if( access(file_name, F_OK) == -1 )
{
removefd( m_epollfd, m_sockfd );
break;
}
/*創建子進程來執行CGI程序*/
ret = fork();
if( ret == -1)
{
removefd( m_epollfd, m_sockfd);
break;
}
else if( ret > 0 )
{
/*父進程只需關閉連接*/
removefd( m_epollfd, m_sockfd);
break;
}
else
{
/*子進程將標準輸出定向到m_sockfd,並執行CGI程序*/
close( STDOUT_FILENO ) ;
dup( m_sockfd );
execl( m_buf, m_buf, 0 );
exit(0);
}
}
}
}
private:
/*讀緩衝區的大小*/
static const int BUFFER_SIZE = 1024;
static int m_epollfd;
int m_sockfd;
sockaddr_in m_address;
char m_buf[ BUFFER_SIZE ];
/*標記讀緩衝區中已經讀入的客戶數據最後一個字節的下一個位置*/
int m_read_idx;
};
int cgi_conn::m_epollfd = -1;
/*主函數*/
int main( int argc, char* argv[] )
{
if( argc <= 2)
{
printf( "usage: %s ip_address port_number\n", basename(argv[0]) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert( listenfd >= 0 );
int ret = 0;
struct sockaddr_in address;
bzero( &address, sizeof(address) );
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons( port );
ret = bind(listenfd, (struct sockaddr*)&address, sizeof( address ));
assert( ret != -1);
ret = listen( listenfd, 5 );
assert(ret != -1);
processpool<cgi_conn>* pool = processpool<cgi_conn>::create( listenfd );
if( pool )
{
pool->run();
delete pool;
}
close(listenfd); /*正如前文提到,main函數創建了文件描述符listenfd,那麼就由它親自關閉*/
return 0;
}
線程池主要用於:
1、需要大量的線程來完成任務,且完成任務的時間比較短。 WEB服務器完成網頁請求這樣的任務,使用線程池技術是非常合適的。因爲單個任務小,而任務數量巨大,你可以想象一個熱門網站的點擊次數。但對於長時間的任務,比如一個Telnet連接請求,線程池的優點就不明顯了。因爲Telnet會話時間比線程的創建時間大多了。
2、對性能要求苛刻的應用,比如要求服務器迅速響應客戶請求。
3、接受突發性的大量請求,但不至於使服務器因此產生大量線程的應用。突發性大量客戶請求,在沒有線程池情況下,將產生大量線程,雖然理論上大部分操作系統線程數目最大值不是問題,短時間內產生大量線程可能使內存到達極限,並出現”OutOfMemory”的錯誤。
線程池優點:
首先說一下多線程的好處:
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力。
我們知道應用程序創建一個對象,然後銷燬對象是很耗費資源的。創建線程,銷燬線程,也是如此。因此,我們就預先生成一些線程,等到我們使用的時候在進行調度,於是,一些”池化資源”技術就這樣的產生了。
本文所提到服務器程序是指能夠接受客戶請求並能處理請求的程序,而不只是指那些接受網絡客戶請求的網絡服務器程序。
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力。但如果對多線程應用不當,會增加對單個任務的處理時間。
可以舉一個簡單的例子:
假設在一臺服務器完成一項任務的時間爲T
T1 創建線程的時間
T2 在線程中執行任務的時間,包括線程間同步所需時間
T3 線程銷燬的時間
顯然T = T1+T2+T3。注意這是一個極度簡化的假設。
可以看出T1,T3是多線程本身的帶來的開銷,我們渴望減少T1,T3所用的時間,從而減少T的時間。
但一些線程的使用者並沒有注意到這一點,所以在程序中頻繁的創建或銷燬線程,這導致T1和T3在T中佔有相當比例。顯然這是突出了線程的弱點(T1,T3),而不是優點(併發性)。
線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的。它把T1,T3分別安排在服務器程序的啓動和結束的時間段或者一些空閒的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。
線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目。
在看一個例子:
假設一個服務器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。我們比較利用線程池技術和不利於線程池技術的服務器處理這些請求時所產生的線程總數。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目或者上限(以下簡稱線程池尺寸),而如果服務器不利用線程池來處理這些請求則線程總數爲50000。一般線程池尺寸是遠小於50000。所以利用線程池的服務器程序不會爲了創建50000而在處理請求時浪費時間,從而提高效率。
線程池的簡單實現:
//Thread.h
#ifndef __THREAD_H
#define __THREAD_H
#include <vector>
#include <string>
#include <pthread.h>
using namespace std;
/**
* 執行任務的類,設置任務數據並執行
*/
class CTask
{
protected:
string m_strTaskName; /** 任務的名稱 */
void* m_ptrData; /** 要執行的任務的具體數據 */
public:
CTask(){}
CTask(const string& taskName)
{
m_strTaskName = taskName;
m_ptrData = NULL;
}
virtual ~CTask(){}
virtual int Run() = 0;
void SetData(void* data); /** 設置任務數據 */
};
/**
* 線程池管理類的實現
*/
class CThreadPool
{
private:
static vector<CTask*> m_vecTaskList; /** 任務列表 */
static bool shutdown; /** 線程退出標誌 */
int m_iThreadNum; /** 線程池中啓動的線程數 */
pthread_t *pthread_id;
static pthread_mutex_t m_pthreadMutex; /** 線程同步鎖 */
static pthread_cond_t m_pthreadCond; /** 線程同步的條件變量 */
protected:
static void* ThreadFunc(void * threadData); /** 新線程的線程回調函數 */
static int MoveToIdle(pthread_t tid); /** 線程執行結束後,把自己放入到空閒線程中 */
static int MoveToBusy(pthread_t tid); /** 移入到忙碌線程中去 */
int Create(); /** 創建線程池中的線程 */
public:
CThreadPool(int threadNum = 10);
int AddTask(CTask *task); /** 把任務添加到任務隊列中 */
int StopAll(); /** 使線程池中的線程退出 */
int getTaskSize(); /** 獲取當前任務隊列中的任務數 */
};
#endif
//Thread.cpp
#include "Thread.h"
#include <iostream>
#include "stdlib.h"
void CTask::SetData(void * data)
{
m_ptrData = data;
}
vector<CTask*> CThreadPool::m_vecTaskList; //任務列表
bool CThreadPool::shutdown = false;
pthread_mutex_t CThreadPool::m_pthreadMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t CThreadPool::m_pthreadCond = PTHREAD_COND_INITIALIZER;
/**
* 線程池管理類構造函數
*/
CThreadPool::CThreadPool(int threadNum)
{
this->m_iThreadNum = threadNum;
cout << "I will create " << threadNum << " threads" << endl;
Create();
}
/**
* 線程回調函數
*/
void* CThreadPool::ThreadFunc(void* threadData)
{
pthread_t tid = pthread_self();
while (1)
{
pthread_mutex_lock(&m_pthreadMutex);
while (m_vecTaskList.size() == 0 && !shutdown)
{
pthread_cond_wait(&m_pthreadCond, &m_pthreadMutex);
}
if (shutdown)
{
pthread_mutex_unlock(&m_pthreadMutex);
printf("thread %lu will exit\n", pthread_self());
pthread_exit(NULL);
}
printf("tid %lu run\n", tid);
vector<CTask*>::iterator iter = m_vecTaskList.begin();
/**
* 取出一個任務並處理之
*/
CTask* task = *iter;
if (iter != m_vecTaskList.end())
{
task = *iter;
m_vecTaskList.erase(iter);
}
pthread_mutex_unlock(&m_pthreadMutex);
task->Run(); /** 執行任務 */
printf("tid:%lu idle\n", tid);
}
return (void*)0;
}
/**
* 往任務隊列裏邊添加任務併發出線程同步信號
*/
int CThreadPool::AddTask(CTask *task)
{
pthread_mutex_lock(&m_pthreadMutex);
this->m_vecTaskList.push_back(task);
pthread_mutex_unlock(&m_pthreadMutex);
pthread_cond_signal(&m_pthreadCond);
return 0;
}
/**
* 創建線程
*/
int CThreadPool::Create()
{
pthread_id = (pthread_t*)malloc(sizeof(pthread_t) * m_iThreadNum);
for(int i = 0; i < m_iThreadNum; i++)
{
pthread_create(&pthread_id[i], NULL, ThreadFunc, NULL);
}
return 0;
}
/**
* 停止所有線程
*/
int CThreadPool::StopAll()
{
/** 避免重複調用 */
if (shutdown)
{
return -1;
}
printf("Now I will end all threads!!\n");
/** 喚醒所有等待線程,線程池要銷燬了 */
shutdown = true;
pthread_cond_broadcast(&m_pthreadCond);
/** 阻塞等待線程退出,否則就成殭屍了 */
for (int i = 0; i < m_iThreadNum; i++)
{
pthread_join(pthread_id[i], NULL);
}
free(pthread_id);
pthread_id = NULL;
/** 銷燬條件變量和互斥體 */
pthread_mutex_destroy(&m_pthreadMutex);
pthread_cond_destroy(&m_pthreadCond);
return 0;
}
/**
* 獲取當前隊列中任務數
*/
int CThreadPool::getTaskSize()
{
return m_vecTaskList.size();
}
//main.cpp
#include "Thread.h"
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
class CMyTask: public CTask
{
public:
CMyTask(){}
inline int Run()
{
printf("%s\n", (char*)this->m_ptrData);
sleep(10);
return 0;
}
};
int main()
{
CMyTask taskObj;
char szTmp[] = "this is the new thread running";
taskObj.SetData((void*)szTmp);
CThreadPool threadPool(10);
for(int i = 0; i < 20; i++)
{
threadPool.AddTask(&taskObj);
}
while(1)
{
printf("there are still %d tasks need to handle\n", threadPool.getTaskSize());
if (threadPool.getTaskSize() == 0)
{
if (threadPool.StopAll() == -1)
{
printf("Now I will exit from main\n");
exit(0);
}
}
sleep(2);
}
return 0;
}