线程池

本文探讨了线程池和进程池的设计理念,详细解释了它们如何在服务器启动时创建固定数量的线程或进程,以高效地处理客户端请求。通过避免频繁的创建和销毁过程,线程池和进程池能显著提升服务器性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程池

      池:提前申请的大量资源存放的一个单位
      内存池:使用内存之前,先申请大量的内存,当后续要使用时,直接从内存池里面分配,不需要通过系统分配。
      服务器启动则创建 n(固定值,有限值)个子进程或者函数线程,创建的子进程或函数线程在服务器终止时销毁,如果有客户端连接,则分配一个子进程或线程为其服务,服务完成之后则继续等待分配下一个客户端。

设计思想
      在服务器启动时,创建出多个进程或者线程,将其维护在池中。当有客户链接时,就从池中分配进程或者线程为客户端服务。
主线程或父进程负责与客户端链接,函数线程或子进程特定客户端交互。

实现难点:
1、主线程需要将文件描述符传递给函数线程
2、函数线程如果没有客户链接时,阻塞在获取文件描述符那里。
3、信号量控制主线程向函数线程通知获取文件描述符事件。
4、主线程在数组中插入数据以及函数线程获取数组中的数据都必须是互斥的。

问题:
一个线程开始为一个客户端服务时,只能等客户端退出才能服务下一个客户端。对于线程资源是一种浪费。

线程池比多线程的优势

  1、创建的进程或者线程是有限的:服务器的系统代价比较小,一般不会达到系统限制的值。
  2、服务器不需要顿繁的创建、销毁进程或线程,只在服务器启动时创建,结束时销毁。
  3、创建的进程或者线程不是为一个客户端服务,可以串行为多个客户端服务。
  4、客户端连接上以后,不需要再去创建进程或线程,只需要分配进程池或线程池中的进程或线程,对于客户端的速度就能快一些。

1、主线程和所有的函数线程同步执行通过信号量
2、线程通过信号量 P 操作阻塞在线程池中
3、如何分配一个线程为客户端服务? 主线程接受一个客户端连接执行V操作
4、主线程如何传递客户端连接的文件描述符?全局维护一个数组或链表

Sever

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

#define THREADNUM 3
#define CLIENTNUM 10
sem_t sem;
pthread_mutex_t mutex;

int ClientFds[CLIENTNUM];

void Init_ClientFds()
{
	int i=0;
	for(;i<CLIENTNUM;++i)
	{
		ClientFds[i]=-1;
	}
}

void Insert_Client(int fd)
{
	pthread_mutex_lock(&mutex);

	int i=0;
	for(;i<CLIENTNUM;i++)
	{
		if(ClientFds[i]==-1)
		{
			ClientFds[i]=fd;
			break;
		}
	}
	pthread_mutex_unlock(&mutex);
}

int Get_Client()
{
	pthread_mutex_lock(&mutex);

	int i=0,c=-1;
	for(;i<CLIENTNUM;++i)
	{
		if(ClientFds[i]!=-1)
		{
			c=ClientFds[i];
			ClientFds[i]=-1;
			break;
		}
	}
	pthread_mutex_unlock(&mutex);

	return c;
}
void*DealClient(void *arg)
{
	while(1)
	{
		//函数启动之前阻塞		//对信号量的P操作
		sem_wait(&sem);
		//服务器维护的与客户端链接的文件描述符
		int c=Get_Client();
		//与客户端交互
		while(1)
		{
			char buff[128]={0};
			int n=recv(c,buff,127,0);
			if(n==0)
			{
				printf("client unlink\n");
				close(c);
				break;//要继续接收下一个连接
			}

			printf("n==%d: %s",n,buff);
			send(c,"OK",2,0);
		}
	}
}

int main()
{
	int listenfd=socket(AF_INET,SOCK_STREAM,0);
	assert(-1!=listenfd);

	struct sockaddr_in ser,cli;
	memset(&ser,0,sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(6000);
	inet_aton("127,0,0,1",(struct in_addr*)&ser.sin_addr);

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

	listen(listenfd,5);
	
	sem_init(&sem,0,0);
	pthread_mutex_init(&mutex,NULL);
	Init_ClientFds();

	//创建线程池
	int i=0;
	for(;i<THREADNUM;++i)
	{
		pthread_t id;
		int res=pthread_create(&id,NULL,DealClient,NULL);
		assert(res==0);
	}

	while(1)
	{
		struct sockaddr_in cli;
		int len=sizeof(cli);

		int c=accept(listenfd,(struct sockaddr*)&cli,&len);
		assert(c!=-1);

		Insert_Client(c);//将c传递给函数线程
		sem_post(&sem);//V操作
	}
}

在这里插入图片描述

进程池

1、程序启动时,创建多个进程,将子进程维护在进程池
2、进程池中的进程必须阻塞在获取客户端文件描述符之前
3、主进程负责接收客户端链接,并将获取到的客户链接文件描述符传递给进程池中的进程,必须借助于进程间通讯。–》不能仅仅传递 C 值,传递的是文件描述符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值