此文與《linux高性能服務器編程》—— 一種高效的異步進程池的實現 是姊妹篇。
(1)線程池框架
(2)源代碼及註釋
#ifndef THREADPOOL
#define THREADPOOL
#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 <pthread.h>
#include <set>
class thread //工作線程類
{
public:
pthread_t tid; //線程標識
int pipefd[2]; //與主線程通信的管道
};
template <typename T> //T是客戶類
class threadpool //線程池類
{
private:
static const int MAX_THREAD_NUMBER = 16; //工作線程的最大數量
static const int USER_PER_THREAD = 1000; //每個工作線程負責的客戶的最大數量
static const int MAX_EVENT_NUMBER = 1000; //epoll內核事件表所能容納的事件的最大數量
int thread_number; //工作線程的實際數量
int listenfd; //指向監聽socket的fd,主/工作線程共用同一個
thread* work_thread; //管理各工作線程的信息
static threadpool* instance; //指向此類的唯一對象
private:
threadpool(int _listenfd, int _thread_number = 8);
static void* worker(void* arg); //工作線程的執行函數
public:
static threadpool* create(int listenfd, int thread_number = 8)
//返回此線程池類的唯一對象
{
if(!instance)
instance = new threadpool(listenfd, thread_number);
return instance;
}
static threadpool* get_instance()
{
return instance;
}
~threadpool()
{
delete[] work_thread;
}
void run(); //執行主線程的任務
};
template <typename T>
threadpool<T>* threadpool<T>::instance = NULL;
int sig_pipefd[2]; //信號管道,主/工作線程都需要
int setnonblocking(int fd) //將fd設爲非阻塞
{
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
void addfd(int epollfd, int fd, std::set<int> &used_fd)
//將fd的讀事件插入epoll內核事件表內,並打開此fd的ET模式,並用used_fd記錄此fd
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN |EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
used_fd.insert(fd);
setnonblocking(fd);
}
void removefd(int epollfd, int fd, std::set<int> &used_fd)
//將fd的所有事件從epoll內核事件表內移除,並從used_fd中移去fd
{
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
used_fd.erase(fd);
}
void close_fd(int epollfd, std::set<int> &used_fd)
//從epoll內核事件表內除去所有的fd及相關事件
{
for(auto fd : used_fd)
{
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}
used_fd.clear();
close(epollfd);
}
void sig_handler(int sig)
//信號捕捉函數,即接收到此信號時,把信號從[1]端送入信號管道內,實現統一事件源
{
int save_errno = errno;
int msg = sig;
send(sig_pipefd[1], (char*)&msg, 1, 0);
errno = save_errno;
}
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);
}
template <typename T>
threadpool<T>::threadpool(int _listenfd, int _thread_number)
: thread_number(_thread_number), listenfd(_listenfd)
{
assert((thread_number > 0) && (thread_number <= MAX_THREAD_NUMBER));
work_thread = new thread[thread_number]; //管理各工作進程線程的信息的數組
assert(work_thread);
for(int i = 0;i < thread_number;++i)
{
//使主線程與各工作線程的通信管道變爲全雙工
int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, work_thread[i].pipefd);
assert(ret == 0);
//將此線程的數據傳遞給其執行函數
ret = pthread_create(&work_thread[i].tid, NULL, worker, work_thread+i);
assert(ret == 0);
//使主線程與工作線程分離,工作線程結束運行後其資源自動被回收
ret = pthread_detach(work_thread[i].tid);
assert(ret == 0);
}
}
template <typename T>
void* threadpool<T>::worker(void *arg)
//工作線程的執行函數
{
//此線程的數據
thread* cur_thread = (thread*)arg;
//用於保存被插入到epoll內核事件表內的fd
std::set<int> used_fd;
int epollfd = epoll_create(5);
assert(epollfd != -1);
//將和主進程通信的管道[0]端的讀事件插入epoll內核事件表內
int pipefd = cur_thread->pipefd[0];
addfd(epollfd, pipefd, used_fd);
epoll_event events[MAX_EVENT_NUMBER];
//保存客戶數據的數組
T *users = new T[USER_PER_THREAD];
assert(users);
int ret;
bool stop = false;
while(!stop)
{
int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if((number < 0) && (errno != EINTR))
{
printf("epoll failure\n");
break;
}
for(int i = 0;i < number && !stop;++i)
{
int sockfd = events[i].data.fd;
if((sockfd == pipefd) && (events[i].events & EPOLLIN))
//來自主進程的數據,說明有客戶發起連接請求
{
int client = 0;
ret = recv(sockfd, (char*)&client, sizeof(client), 0);
if(ret <= 0)
{
continue;
}
else
//接受客戶連接請求
{
struct sockaddr_in address;
socklen_t addrlength = sizeof(address);
int connfd = accept(get_instance()->listenfd, (struct sockaddr*)&address,
&addrlength);
if(connfd < 0)
{
printf("error is %d\n", errno);
continue;
}
else if(connfd >= USER_PER_THREAD)
//此線程管理的用戶數量已達到上限,關閉此連接
{
const char *msg = "sorry!";
send(connfd, msg, sizeof(msg), 0);
close(connfd);
continue;
}
//將此連接socket的讀事件插入epoll內核事件表內
addfd(epollfd, connfd, used_fd);
//初始化客戶數據,以連接socket的fd值作爲客戶序號
users[connfd].init(epollfd, connfd, address);
}
}
else if(events[i].events & EPOLLIN)
//來自某連接socket的數據,說明客戶發起了某個請求
{
//調用客戶類的方法進行處理
users[sockfd].process(used_fd);
}
}
}
delete[] users; //回收動態資源
close_fd(epollfd, used_fd); //關閉所有fd
cur_thread->tid = -1; //在線程信息數組中標記此線程已結束運行
pthread_exit(NULL);
}
template <typename T>
void threadpool<T>::run()
//主線程運行的代碼
{
//用於保存被插入到epoll內核事件表內的fd
std::set<int> used_fd;
int epollfd = epoll_create(5);
assert(epollfd != -1);
//將信號管道設爲全雙工模式
int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sig_pipefd);
assert(ret != -1);
setnonblocking(sig_pipefd[1]);
//將信號管道的讀事件插入epoll內核事件表內,實現統一事件源
addfd(epollfd, sig_pipefd[0], used_fd);
//捕捉以下信號
addsig(SIGTERM, sig_handler);
addsig(SIGINT, sig_handler);
//忽略此信號
addsig(SIGPIPE, SIG_IGN);
//將監聽socket的讀事件插入epoll內核事件表內
addfd(epollfd, listenfd, used_fd);
epoll_event events[MAX_EVENT_NUMBER];
//挑選工作線程的起點序號
int sub_thread_counter = 0;
int number = 0;
bool stop = false;
while(!stop)
{
number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if((number < 0) && (errno != EINTR))
{
printf("epoll failure\n");
break;
}
for(int i = 0;i < number && !stop;++i)
{
int sockfd = events[i].data.fd;
if(sockfd == listenfd)
//來自監聽socket的數據,說明有客戶發起連接請求
{
int j = sub_thread_counter;
do
//採用Round Robin方式挑選出一個工作線程
{
if(work_thread[j].tid != -1)
break;
j = (j + 1) % thread_number;
} while(j != sub_thread_counter);
if(work_thread[j].tid == -1)
//所有工作線程已結束運行則主線程也退出
{
stop = true;
break;
}
sub_thread_counter = (j + 1) % thread_number;
//向挑選出的工作線程通知:有客戶發起連接請求
int msg = 1;
send(work_thread[j].pipefd[1], (char*)&msg, sizeof(msg), 0);
}
else if((sockfd == sig_pipefd[0]) && (events[i].events & EPOLLIN))
//來自信號管道的數據
{
char signals[1024];
ret = recv(sig_pipefd[0], signals, sizeof(signals), 0);
if(ret <= 0)
continue;
else
{
for(int j = 0;j < ret && !stop;++i)
{
switch(signals[j])
{
case SIGTERM:
case SIGINT:
//收到中止信號,結束前先殺死所有工作線程
{
for(int k = 0;k < thread_number;++k)
{
unsigned long tid = work_thread[k].tid;
if(tid != -1)
{
pthread_cancel(tid);
}
}
stop = true;
break;
}
}
}
}
}
}
}
//關閉所有fd
close_fd(epollfd, used_fd);
close(sig_pipefd[1]);
}
#endif
(3)用於測試線程池的簡單客戶類的實現
class client //客戶類
{
private:
static const int BUFFER_SIZE = 1024; //緩衝區大小
int epollfd; //負責此客戶的工作進程的epollfd
sockaddr_in client_addr; //客戶socket地址
char buf[BUFFER_SIZE]; //緩衝區
public:
int connfd; //與此客戶通信的socket
void init(int _epollfd, int _connfd, const sockaddr_in& _client_addr)
//初始化客戶數據
{
epollfd = _epollfd;
connfd = _connfd;
client_addr = _client_addr;
memset(buf, '\0', BUFFER_SIZE);
}
void process(std::set<int> &used_fd)
//當客戶有數據發來時,進程的迴應:把客戶發來的數據原封不動地返回給客戶,僅用於測試
{
memset(buf, '\0', sizeof(buf));
int ret = recv(connfd, buf, sizeof(buf), 0);
if((ret < 0 && errno != EAGAIN) || ret == 0)
//客戶關閉連接或發生讀錯誤,則關閉連接
{
//回收文件描述符
removefd(epollfd, connfd, used_fd);
close(connfd);
}
else if(ret > 0)
{
//將數據返還給客戶
send(connfd, buf, strlen(buf), 0);
}
}
};