线程池和进程池

动态创建子进程(函数线程)实现并发服务器的缺点

在前面的文章中我们是通过动态创建子进程(函数线程)来实现并发服务器的,这样做的缺点如下:

  1. 动态创建进程(或线程)是比较耗费时间的,这样导致较慢的客户响应
  2. 动态创建的子进程(子线程)通常只用来为一个客户服务,这将导致系统上产生大量的细微进程(或线程)。进程间的切换将消耗大量的CPU时间
  3. 动态创建的子进程是当前进程的完整映像,当前进程必须谨慎地管理其分配的文件描述符和堆内存等系统资源,否则子进程可能复制这些资源,从而使系统的可用资源急剧下降,进而影响服务器的性能

为了解决这些问题,我们使用了进程池和线程池这两种技术。

进程池和线程池概述

  • 设计思想:进程池是由服务器预先创建的一组子进程,在服务器程序启动时就创建,当有客户端连接时,就从池中分配进程或者线程为客户端进行服务。这些子进程的典型数目在3~10个之间。httpd守护进程就是使用包含8个子进程的进程池来实现并发的。线程池中的线程数量应该和CPU数量差不多

httpd的进程池:

我们可以清楚地看到,当我们开启httpd服务后,查看进程时,有8个父进程都是9863的子进程,这就是httpd创建的进程池。

  • 优点:
  1. 进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级,PGID等。因为进程池在服务器启动之初就创建好了,所以每个子进程都相对干净,即他们没有打开不必要的文件描述符(从父进程继承而来),也不会错误地使用大块的堆内存。
  2. 当有新的任务到来时,主进程通过某种进程间通讯的方式选择进程池中的某个子进程来为之服务。相比于动态创建,选择一个已有的子进程(函数线程)的代价显然要小得多

线程池的实现难点

  1. 主线程需要将文件描述符,传递给函数线程
  2. 函数线程启动后必须阻塞在获取文件描述符之前
  3. 信号量来控制主线程向函数线程通知获取文件描述符事件
  4. 主线程在数组中插入数据,以及函数线程获取数组中的数据必须是一个互斥的过程

进程池的实现难点

  1. 父进程需要将文件描述符传递给子进程
  2. 由于文件描述符是在fork之后创建,因此父子进程无法共享
  3. 进程池中的进程必须阻塞在获取到客户端的文件描述符之前
  4. 传递文件描述符时,不能仅仅传递一个整型值,而是文件描述符

进程池源码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

static const int CONTROL_LEN = CMSG_LEN(sizeof(int));

/*文件描述符发送函数*/
void send_fd(int fd, int fd_to_send)
{
	struct iovec iov[1];
	struct msghdr msg;
	char buf[0];

	iov[0].iov_base = buf;
	iov[0].iov_len = 1;
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	struct cmsghdr cm;
	cm.cmsg_len = CONTROL_LEN;
	cm.cmsg_level = SOL_SOCKET;
	cm.cmsg_type = SCM_RIGHTS;
	*(int *)CMSG_DATA(&cm) = fd_to_send;
	msg.msg_control = &cm;
	msg.msg_controllen = CONTROL_LEN;

	sendmsg(fd, &msg, 0);
}


/*文件描述符接收函数*/
int recv_fd(int fd)
{
	struct iovec iov[1];
	struct msghdr msg;
	char buf[0];

	iov[0].iov_base = buf;
	iov[0].iov_len = 1;
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	struct cmsghdr cm;
	msg.msg_control = &cm;
	msg.msg_controllen = CONTROL_LEN;

	recvmsg(fd, &msg, 0);

	int fd_to_read = *(int*)CMSG_DATA(&cm);

	return fd_to_read;
}

int main()
{
	int pipefd[2];
	int res = socketpair(PF_UNIX, SOCK_DGRAM, 0, pipefd);//创建管道为发送文件描述符做准备
	int sockfd = socket(PF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);

	struct sockaddr_in ser, cli;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6500);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
	assert(res != -1);

	listen(sockfd, 5);
	
	int ret;
	int i = 0;
/*接收连接前先创建进程池*/
	for(; i < 3; i++)
	{
		ret = fork();
		if(ret == 0)
		{
			break;
		}
	}
	
	if(ret != 0)
	{
		close(pipefd[0]);//父进程关闭读通道
		while(1)
		{
			socklen_t len = sizeof(cli);
			int c = accept(sockfd, (struct sockaddr*)&cli, &len);
			if(c == -1)
			{
				printf("accept error\n");
				continue;
			}
			send_fd(pipefd[1], c);//将接收到的文件描述符写入管道,供子进程读取
			close(c);
		}
	}
	if(ret == 0)
	{
		while(1)
		{
			close(pipefd[1]);//子进程关闭写通道
			int c = recv_fd(pipefd[0]);//读取管道中的文件描述符
			while(1)
			{
				char recvbuff[128] = {0};
				res = recv(c, recvbuff, 127, 0);
				if(res <= 0)
				{
					printf("%ddisclient\n", c);
					close(c);
					break;
				}
				printf("%d: %s\n", c, recvbuff);
				send(c, "OK", 2, 0);
			}
		}
	}
}

线程池源码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<semaphore.h>

pthread_mutex_t mutex;
int clilink[8];//已经被接收但还未被处理的连接请求
sem_t sem;
/*初始化连接数组*/
void InitCliLink()
{
	int i = 0;
	for(; i < 8; i++)
	{
		clilink[i] = -1;
	}
}

/*插入新接收的连接,等待处理*/
int Insert(int c)
{
	pthread_mutex_lock(&mutex);//插入的同时时不能获取,用锁控制
	int i = 0;
	for(; i < 8; i++)
	{
		if(clilink[i] == -1)
		{
			clilink[i] = c;
			break;
		}
	}
	pthread_mutex_unlock(&mutex);
	if(i >= 8)
		return -1;
	return 0;
}

/*获取连接,进行处理*/
int GetCli()
{
	pthread_mutex_lock(&mutex);//获取时不能插入
	int i = 0;
	int c = clilink[0];
	for(; i < 7; i++)
	{
		clilink[i] = clilink[i+1];
	}
	pthread_mutex_unlock(&mutex);
	return c;
}

void *pthread_fun(void *arg);

int main()
{
	int sockfd = socket(PF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);

	struct sockaddr_in ser, cli;
	memset(&ser, 0, sizeof(ser));
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6500);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
	assert(res != -1);

	listen(sockfd, 5);


/*创建线程池*/
	int i = 0;
	for(; i < 3; i++)
	{
		pthread_t id;
		res = pthread_create(&id, NULL, pthread_fun, NULL);
	}
	
	InitCliLink();
	sem_init(&sem, 0, 0);//初始化信号量值为0,使得函数线程阻塞

	while(1)
	{
		int len = sizeof(cli);
		int c = accept(sockfd, (struct sockaddr*)&cli, &len);
		if(c < 0)
		{
			continue;
		}
		
		if(Insert(c) == -1)
		{
			close(c);
			continue;
		}
		sem_post(&sem);//信号量+1,函数线程可以开始获取数组中等待的连接进行处理
	}
}

/*线程函数*/
void *pthread_fun(void *arg)
{
	while(1)
	{
		sem_wait(&sem);
		int c = GetCli();
			while(1)
			{
				char buff[128] = {0};
				int n = recv(c, buff, 127, 0);
				if(n <= 0)
				{
					close(c);
					break;
				}
				printf("%d: %s\n",c, buff);
				send(c, "ok", 2, 0);
			}
	}
}

猜你喜欢

转载自blog.csdn.net/Mr_H9527/article/details/84706894