一、池的概念
由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间,即“浪费”服务器的硬件资源,以换取其运行效率。这就是池的概念。池是一组资源的集合,这组资源在服务器启动之初就完全被创建并初始化,这称为静态资源分配。当服务器进入正式运行阶段,即开始处理客户请求的时候,如果它需要相关的资源,就可以直接从池中获取,无需动态分配。很显然,直接从池中取得所需资源比动态分配资源的速度要快得多,因为分配系统资源的系统调用都是很耗时的。当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用来释放资源。从最终效果来看,池相当于服务器管理系统资源的应用设施,它避免了服务器对内核的频繁访问。
二、常见的池
平常我们使用new、malloc在堆区申请一块内存,但由于每次申请的内存大小不一样就会产生很多内存碎片,造成不好管理与浪费的情况。内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。
这两个问题有一定的相似度,在面向对象程序编程中,对象的创建与析构都是一个较为复杂的过程,较费时间,所以为了提高程序的运行效率尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。
所以我们可以创建一个进程池(线程池),预先放一些进程(线程)进去,要用的时候就直接调用,用完之后再把进程归还给进程池,省下创建删除进程的时间,不过当然就需要额外的开销了。
利用线程池与进程池可以使管理进程与线程的工作交给系统管理,不需要程序员对里面的线程、进程进行管理。
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而在处理请求时浪费时间,从而提高效率。
三、多进程池的服务
每次请求都生成新进程其实必要性并不大,大部分并发服务器处理的每秒并发量一般最多就在几百左右,因此一般几个或者十几个进程循环提供服务就可以hold住,为了减少每次请求建立新进程的成本,我们的前辈又发明了多进程池(prefork)的模式,预先生成若干进程来处理请求。
tcp_server.c:
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_CONNECTION 2
int main(int argc, char** argv){
int fd = -1;
time_t ticks;
pid_t pid;
pid_t pids[10];//后面将会一次产生十个子进程,存放每个子进程的id
int port = 99999;//指定端口号为99999
//父进程创建监听套接字
fd = socket(AF_INET,SOCK_STREAM,0);
//绑定套接字与ip地址和端口号
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
int size = sizeof(struct sockaddr);
bind(fd,(struct sockaddr*)&addr,size);
//父进程监听客户端的请求
listen(fd,MAX_CONNECTION);
//创建子进程
int i;
for(i = 0; i< 10;i++){
pid = fork();
if(pid > 0){ continue;//父进程继续循环创建子进程
}
//子进程处理客户端的请求
pids[i] = pid;
struct sockaddr_in client_addr;
while (1){
memset(&client_addr, 0, sizeof (client_addr));
char buf[1024];
memset(&buf,0,sizeof(buf));
//创建new_socket来处理客户端的请求
int client_fd = accept(fd, (struct sockaddr *) &client_addr, &size);
close(fd);//关闭监听文件(不必要的文件)
ticks = time (0);
//打印时间
snprintf(buf, sizeof(buf), "%s", ctime(&ticks));
//将buf里的内容写到网络中
write(client_fd, buf, sizeof(buf));
sleep(3600);
//客户端请求结束,关闭文件
close(client_fd);
}
}
close(fd);
//等待回收子进程
for(i = 0; i< 10;i++){
int status;
if(pids[i] < 0){
continue;
}
waitpid(pids[i],&status,0);
}
return 0;
}
tcp_client.c:
#include
#include
#include
#include
#include
#include
#include
void static Usage(const char* proc)
{
printf("Usage: %s [local_ip] [local_port]\n",proc);
}
//./tcp_clinet servet_ip servet_port
int main(int argc,char* argv[])
{
if(argc!=3){
Usage(argv[1]);
exit(1);
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0 ){
perror("socket");
exit(2);
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){
perror("connect");
exit(3);
}
char buf[1024];
while(1){
printf("Please Enter$");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s>0){
buf[s-1] = 0;
write(sock,buf,strlen(buf));
s = read(sock,buf,sizeof(buf)-1);
if(s>0){
buf[s] = 0;
printf("servetr echo#:%s\n",buf);
}
}
return 0;
}
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define THREAD_MAX 20
#define LISTEN_MAX 20
#define SERVER_IP "127.0.0.1"
typedef struct {
char ip4[128];
int port;
int fd;
} LISTEN_INFO;
//服务器参数
static LISTEN_INFO s_listens[LISTEN_MAX];
//线程池参数
static unsigned int s_thread_para[THREAD_MAX][8];//线程参数
static pthread_t s_tid[THREAD_MAX];//线程ID
pthread_mutex_t s_mutex[THREAD_MAX];//线程锁
//私有函数
static int init_thread_pool(void);//初始化数据
static int init_listen4(char *ip4, int port, int max_link); //初始化监听
//线程函数
void * test_server4(unsigned int thread_para[]);
//设置文件描述符为NonBlock
bool setNonBlock(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
if(-1 == fcntl(fd, F_SETFL, flags))
return false;
return true;
}
int main(int argc, char *argv[])//客户端驱动
{
//临时变量
int i, j, rc;
int sock_listen; //监听套接字
int sock_cli; //客户端连接
int listen_index;
int epfd;
int nfds;
struct epoll_event ev;
struct epoll_event events[LISTEN_MAX];
socklen_t addrlen; //地址信息长度
struct sockaddr_in addr4; //IPv4地址结构
//线程池初始化
rc = init_thread_pool();
if (0 != rc) exit(-1);
//初始化服务监听
for(i = 0; i < LISTEN_MAX; i++) {
sprintf(s_listens[i].ip4, "%s", SERVER_IP);
s_listens[i].port = 40000 + i;
//创建监听
rc = init_listen4(s_listens[i].ip4, s_listens[i].port, 64);
if (0 > rc) {
fprintf(stderr, "无法创建服务器监听于%s:%d\r\n", s_listens[i].ip4, s_listens[i].port);
exit(-1);
} else {
fprintf(stdout, "已创建服务器监听于%s:%d\r\n", s_listens[i].ip4, s_listens[i].port);
}
s_listens[i].fd = rc;
}
//设置集合
epfd = epoll_create(8192);
for (i = 0; i < LISTEN_MAX; i++) {
//加入epoll事件集合
ev.events = EPOLLIN | EPOLLET;
ev.data.u32 = i;//记录listen数组下标
if (epoll_ctl(epfd, EPOLL_CTL_ADD, s_listens[i].fd, &ev) < 0) {
fprintf(stderr, "向epoll集合添加套接字失败(fd =%d)\r\n", rc);
exit(-1);
}
}
//服务循环
for( ; ; ) {
//等待epoll事件
nfds = epoll_wait(epfd, events, LISTEN_MAX, -1);
//处理epoll事件
for(i = 0; i < nfds; i++) {
//接收客户端连接
listen_index = events[i].data.u32;
sock_listen = s_listens[listen_index].fd;
addrlen = sizeof(struct sockaddr_in);
bzero(&addr4, addrlen);
sock_cli = accept(sock_listen, (struct sockaddr *)&addr4, &addrlen);
if(0 > sock_cli) {
fprintf(stderr, "接收客户端连接失败\n");
continue;
} else {
char *myIP = inet_ntoa(addr4.sin_addr);
printf("accept a connection from %s...\n", myIP);
}
setNonBlock(sock_cli);
//查询空闲线程对
for(j = 0; j < THREAD_MAX; j++) {
if (0 == s_thread_para[j][0]) break;
}
if (j >= THREAD_MAX) {
fprintf(stderr, "线程池已满, 连接将被放弃\r\n");
shutdown(sock_cli, SHUT_RDWR);
close(sock_cli);
continue;
}
//复制有关参数
s_thread_para[j][0] = 1;//设置活动标志为"活动"
s_thread_para[j][1] = sock_cli;//客户端连接
s_thread_para[j][2] = listen_index;//服务索引
//线程解锁
pthread_mutex_unlock(s_mutex + j);
}//end of for(i;;)
}//end of for(;;)
exit(0);
}
static int init_thread_pool(void)
{
int i, rc;
//初始化线程池参数
for(i = 0; i < THREAD_MAX; i++) {
s_thread_para[i][0] = 0;//设置线程占用标志为"空闲"
s_thread_para[i][7] = i;//线程池索引
pthread_mutex_lock(s_mutex + i);// 这个地方为什么要加锁?不加锁创建监听有时会不成功
}
//创建线程池
for(i = 0; i < THREAD_MAX; i++) {
rc = pthread_create(s_tid + i, 0, (void* (*)(void *))test_server4, (void *)(s_thread_para[i]));
if (0 != rc) {
fprintf(stderr, "线程创建失败\n");
return(-1);
}
}
//成功返回
return(0);
}
static int init_listen4(char *ip4, int port, int max_link)
{
//临时变量
int sock_listen4;
struct sockaddr_in addr4;
unsigned int optval;
struct linger optval1;
//初始化数据结构
bzero(&addr4, sizeof(addr4));
//inet_pton将点分十进制IP转换为整数
inet_pton(AF_INET, ip4, &(addr4.sin_addr));
addr4.sin_family = AF_INET;
//htons将无符号short从主机字节序(x86:Big-Endian)转换为网络字节序
addr4.sin_port = htons(port);
//创建流类型的SOCKET
sock_listen4 = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sock_listen4) {
fprintf(stderr, "创建socket异常, sock_listen4:%d\n", sock_listen4);
perror("创建socket异常");
return(-1);
}
//设置SO_REUSEADDR选项(服务器快速重起)
optval = 0x1;
setsockopt(sock_listen4, SOL_SOCKET, SO_REUSEADDR, &optval, 4);
//设置SO_LINGER选项(防范CLOSE_WAIT挂住所有套接字)
optval1.l_onoff = 1;
optval1.l_linger = 60;
setsockopt(sock_listen4, SOL_SOCKET, SO_LINGER, &optval1, sizeof(struct linger));
if (0 > bind(sock_listen4, (struct sockaddr *)&addr4, sizeof(addr4))) {
fprintf(stderr, "bind socket异常, sock_listen4:%d\n", sock_listen4);
perror("bind socket异常");
close(sock_listen4);
return(-1);
}
if (0 > listen(sock_listen4, max_link)) {
fprintf(stderr, "listen socket异常, sock_listen4:%d\n", sock_listen4);
perror("listen socket异常");
close(sock_listen4);
return(-1);
}
return (sock_listen4);
}
void * test_server4(unsigned int thread_para[])
{
//临时变量
int sock_cli; //客户端连接
int pool_index; //线程池索引
int listen_index; //监听索引
char buff[32768]; //传输缓冲区
int i, j, len;
char *p;
//线程脱离创建者
pthread_detach(pthread_self());
pool_index = thread_para[7];
wait_unlock:
pthread_mutex_lock(s_mutex + pool_index);//等待线程解锁
//线程变量内容复制
sock_cli = thread_para[1];//客户端连接
listen_index = thread_para[2];//监听索引
//接收请求
len = recv(sock_cli, buff, sizeof(buff), MSG_NOSIGNAL);
printf("%s\n", buff);
//构造响应
p = buff;
//HTTP头
p += sprintf(p, "HTTP/1.1 200 OK\r\n");
p += sprintf(p, "Content-Type: text/html\r\n");
p += sprintf(p, "Connection: closed\r\n\r\n");
//页面
p += sprintf(p, "\r\n\r\n");
p += sprintf(p, "\r\n");
p += sprintf(p, "\r\n");
p += sprintf(p, "\r\n");
p += sprintf(p, "\r\n");
p += sprintf(p, "连接状态
\r\n");
p += sprintf(p, "服务器地址 %s:%d
\r\n", s_listens[listen_index].ip4, s_listens[listen_index].port);
j = 0;
for(i = 0; i < THREAD_MAX; i++) {
if (0 != s_thread_para[i][0]) j++;
}
p += sprintf(p, "线程池状态
\r\n");
p += sprintf(p, "线程池总数 %d 活动线程总数 %d
\r\n", THREAD_MAX, j);
p += sprintf(p, " \r\n");
len = p - buff;
//发送响应
send(sock_cli, buff, len, MSG_NOSIGNAL);
memset(buff, 0, 32768);
//释放连接
shutdown(sock_cli, SHUT_RDWR);
close(sock_cli);
//线程任务结束
thread_para[0] = 0;//设置线程占用标志为"空闲"
goto wait_unlock;
pthread_exit(NULL);
}