高性能服务器编程----进程池&线程池

        高性能服务器编程主要分为多进程和多线程、进程池和线程池,用来处理一个服务程序能够同时处理多个客户连接的问题。我们首先回顾下多进程和多线程的知识,因为进程池和线程池是在这个基础上进行改进的,也是服务器用的比较多的。   

  1. 多进程

           accept();  --》创建子进程,由子进程和客户端通讯。父进程继续接受客户连接

              a.子进程继承父进程打开的文件描述符(accept返回的文件描述符)

              b.父进程必须关闭文件描述符。

      2.多线程

             accept();  --》创建函数线程,由函数线程和客户端通讯,主线程继续接受客户连接

               a.主线程接受客户连接的文件描述符如何传递给函数线程 –》值传递

               b.主线程不能关闭文件描述符  ---》 进程的PCB中保存 同进程的多线程共享一个PCB

       应用场景:  

  •        进程之间是相互独立的,不存在数据安全
  •        进程相对线程而言,创建时,开辟的资源多,CPU调度时,比较慢
  •        如果多进程要通讯,必须借助于特定的手段(信号,信号量,管道,共享内存,消息队列)
  •        Linux上进程中能创建的数量相比于系统进程数量小的多  (一个进程大概有300多线程)

一、进程池和线程池的概念

     :提前申请的大量资源存放的一个单位;

     内存池:  使用内存之前,先申请大量的内存,当后续需要使用时,直接从内存池中分配,不需要通过系统分配。

多进程和多线程与线程池和进程思想对比:

  •  多进程或者多线程: 
  •         有客户连接时,系统为这个客户创建出进程或者线程,当与客户端交互完成时,创建的进程或线程也就随之释放。
  • 进程池或者线程池

  •         a.在服务器程序启动时,创建出多个线程或进程,将其维护在池中。 当有客户连接时,就在池中分配进程或者线程为客户端服务。

  •         b.主线程或者父进程--》负责与客户端连接;函数线程或者子进程---》与特定客户端交互

     我们看下我们linux下池中最多有几个进程:

由此可看有8个进程,通常池中分配大约的3---10个。线程池我们也看一下:

        可看出也是有8个,其实在选择进程池还是线程池,也是根据我们的业务和环境的要求下进行选择。例如如果考虑安全性可以使用进程池,如果考虑切换速度,数据共享的话可以考虑线程池,各有各有好处和应用场景,更适合才是最重要的~

二、线程池的代码实现

我们通过代码再来看看线程池有些什么问题。在这之前,我们要明白线程池实现的几个难点:

  1. 主线程需要将文件描述符传递给函数线程  --》全局的数组
  2. 函数线程启动起来必须阻塞在获取文件描述符之前 --》信号量
  3. 信号量来控制主线程向函数线程通知获取文件描述符事件  --》信号量
  4. 主线程在数组中插入数据,以及函数线程获取数组中的数据都必须是互斥的,保持原子操作。  ---》互斥锁

   

图片是我们线程池的代码形式图,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>

#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>

sem_t sem;
pthread_mutex_t mutex;

void* pthread_fun(void *arg);
int clilink[10];

void 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 i = 0;
	int c = -1;
	for(; i < 10;++i)
	{
		if(clilink[i] != -1)
		{
			 c = clilink[i];
			 clilink[i] = -1;
		     break;
		}
	}
	pthread_mutex_unlock(&mutex);

	if(i >= 10)
		return -1;
	return c;
                 //-1是无效的文件描述符
}

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(6888);
	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;
		}
		//对信号量的V操作 
		sem_post(&sem);
	}
}


void *pthread_fun(void *arg)
{
	while(1)
	{
		//阻塞 对信号量的P操作
		sem_wait(&sem);
		int c = GetCli();
		if(c == -1)
		{
			continue;
		}
		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);
		}
	}

}

  结果如下:

        因为我只维护了三个线程,因此当我开启第四个客户端的时候,它会在阻塞的状态,当我关闭前三个任意一个进程时,会发现它立刻可以执行。比如说关闭third客户端。

     可以看出four立即就被打印了~~


        对于以上代码我们会面临一个问题:后来的客户端有时候会被先服务,我们可以将代码改一下,每次接着遍历下一个,直到完成,再从头开始。保证先来先服务。

int GetCli()
{
	pthread_mutex_lock(&mutex);
	int i = 0;
	int c = clilink[0];
	for(; i < 9;++i)
	{				
		clilink[i] = clilink[i + 1];
	}
        clilink[i] = -1;		   	
	pthread_mutex_unlock(&mutex);

	if(i >= 10)
		return -1;
	return c;
                 //-1是无效的文件描述符
}

三、进程池

  1. 在程序启动时,创建多个进程,将子进程维护在进程池;
  2. 进程池中的进程必须阻塞在获取到客户端文件描述符之前;
  3. 主进程负责接收客户连接,并将获取到的客户连接文件描述符传递给进程池中的进程

          必须借助于进程间通讯  不能仅仅传递c值,传递的是文件描述符--》指向相同的结构

    如何在两个不相干的进程之间传递文件描述符呢? 我们可以利用socket在进程间传递特殊的辅助数据,以实现文件描述符的传递。另外我们还可以结合socketpair全双工管道,以及函数sendmsg  、recvmsg等相关知识学习下进程池的代码。

     有兴趣的小伙伴可以看看《linux高性能服务编程》里的进程的代码实现~

      两者的选择和多进程和多线程一样,需要因情况和场景选择。我们要明白一个线程或者线程开始为一个客户端服务时,只能等客户端退出才能服务下一个客户端,对于线程资源是一种浪费。阻塞在recv函数 。但是I/O复用可以解决同时处理与多个客户端交互的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值