线程池
池:提前申请的大量资源存放的一个单位
内存池:使用内存之前,先申请大量的内存,当后续要使用时,直接从内存池里面分配,不需要通过系统分配。
服务器启动则创建 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 值,传递的是文件描述符。