进程池&线程池

进程池和线程池概述

进程池和线程池相似,所以这里我们以进程池为例介绍,下面对进程池的讨论完全适用于线程池(如果没有特殊声明)。

进程池是由服务器预先创建的一组子进程,这些子进程的数目在3~10个之间(典型情况)。线程池的数量应该和CPU数量差不多。

进程池中的所有子进程都运行者相同的代码,并具有相同的属性。因为进程池在服务器启动之初就创建好了,所以每个子进程都相对“干净”,即它们没有打开不必要的文件描述符(从父进程继承而来),也不会错误的使用大块的堆内存(从父进程复制得到)。

当有新的任务到来时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显然要小得多。至于主进程选择哪个子进程来为新任务服务,有两种方式:

  1. 主进程使用某种算法来中东选择子进程。最简单常用的算法是随机算法和RoundRobin(轮流选取算法),但是更智能的算法将使任务在各个工作进程中更均匀的分配,从而减轻服务器的整体压力。
  2. 主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行,而其他子进程将继续睡眠在工作队列上。

当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方法是在父进程和子进程之间预先建立好一条管道,然后通过该管道来实现所有进程间的通信。在父线程和子线程之间传递数据就要简单的多,因为我们可以吧这些数据定义为全局的,那么它们本身就是被所有线程共享的。

综上所述,我们其实可以用两句话总结,那就是在服务器程序启动时,创建出多个进程或线程,将其维护在池中,当有客户连接时,就从池中分配进程或线程为客户端服务。

 

线程池的实现

线程池实现的难点:

  1. 主线程需要将文件描述符传递给函数线程
  2. 函数线程启动起来后必须阻塞在获取文件描述符之前
  3. 信号量控制主线程向函数线程通知获取文件描述符事件
  4. 主线程在数组中插入数据,以及函数线程获取数组中的数据都必须是互斥的
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
sem_t sem;
pthread_mutex_t mutex;
void *pthread_fun(void *arg);
int clilink[10];
int InitCliLink()
{
	int i=0;
	for(;i<10;++i)
	{
		clilink[i]=-1;
	}
}
int Insert(int c)
{
	pthread_mutex_lock(&mutex);
	int i=0;
	for(;i<10;++i)
	{
		if(clilink[i]==-1);
		{
			clilink[i]=c;
			break;
		}
	}
	pthread_mutex_unlock(&mutex);
	if(i>=10)
		return -1;
	return 0;
}

int GetCli()
{
	pthread_mutex_lock(&mutex);
	int c=clilink[0];
	int i=0;
	for(;i<9||clilink[i]!=-1;++i)
	{
		clilink[i]=clilink[i+1];
	}
	clilink[i]=-1;
	pthread_mutex_unlock(&mutex);
		return c;
}
int main()
{
	int sockfd=socket(AF_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(6000);
	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);
	pthread_mutex_init(&mutex,NULL);
	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);//对信号量的V操作
	}
}
void *pthread_fun(void *arg)
{
	while(1)
	{
		sem_wait(&sem);//阻塞 对信号量的p操作
		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);
		}
	}
}






进程池的实现

  1. 在程序启动时,创建多个进程,将子进程维护在进程池

  2. 进程池中的进程必须阻塞在获取到文件描述符之前

  3. 主进程负责接收客户连接,并将获取到的客户连接文件描述符传递给进程池中的进程

  4. 必须借助于进程间通讯,不能仅仅传递C值,传递的是文件描述符

 

你可能感兴趣的:(Linux,操作系统)