多线程服务器编程模型

1.关注的3.5个问题

  1. 连接的建立
  2. 连接的断开
  3. 消息到达
  4. 消息发送完毕

2.服务流程

客户端:socket、connnect、send、recv
服务端:socket、bind、listen、accept、recv、send、close
阻塞IO和非阻塞IO:没有数据的时候是不是立即返回

2.1 优化方案1:为每一条连接,创建一个线程。

2.2 优化方案2:reactor网络模型

1.组成:非阻塞IO+IO多路复用(复用的网络线程)
linux下的IO多路复用模型
select:需要轮询检查每一个IO是否有事件。
poll:将读、写、错误分成三种类型去检测
epoll:将读、写、操作事件集中到一个阻塞中去完成,并且返回有事件的IO。
2.特征:事件循环+事件驱动/回调
3.缺点:容易分裂业务逻辑
4.解决办法:协程:同步非阻塞

2.2.1 单线程reactor结构

//创建监听套接字
listenfd=socket();
//绑定监听套接字
bind(listenfd,addr);
//创建epollIO多路复用
efd=epoll_create(0);
//将listenfd放到efd中管理
epoll_clt(efd,epoll_ctl_add,listenfd,&ev);
while(1){
	epoll_event ev[];
	int nevents=epoll_wait(efd,ev,events,timeout);
	for(int i;i<nevents;i++)
	{
		epoll_event *e=&ev[i];
		if(e->fd==listenfd)
		{
			//建立连接的事件
		}else {//读、写、错误事件
			if(e->events & epollin||epollhub){//可读
			}
			if(e->events & epollout){//可写
			}
			if(e->events & epollerr){//错误事件
			}
		}
	}
}

epoll原理
epoll_create() 在内核中创建了一个红黑树,去管理注册的事件;还创建了一个就绪队列,这是一个双向队列,如果事件发生了,放到就绪队列里。
epoll_ctl() 对红黑树进行操作。
epoll_wait() 将网络中就绪的事件拷贝到events数组中。

reactor的变种1:nginx去fork出多个进程去处理。
master进程fork()出多个worker进程(CPU核心数) 。通过
共享内存+进程锁**,worker进程依次获得处理连接的权利。优化:如果一个worker进程处理了7/8*conntect,就不会去获得进程锁。负载均衡。
问题1:nginx为什么使用多进程?
nginx是静态web服务器,处理http请求,连接之间一般没有关联(除了游戏,玩家之间的交互),隔离性强,使用slab共享内存(在共享内存中加一些数据结构:数组、红黑树),简单数据统一。
运行环境隔离性+数据统一性
问题2:nginx为什么不适用多进程+多线程?
fork()不能在多线程中使用,linux当中只能克隆当前线程,不能克隆其他线程的内容:malloc。其它系统调用,加锁。

reactor的变种2:reactor+队列+线程池:skynet,线程池消耗队列,处理业务逻辑。

2.2.2 one loop per thread模型

每一个线程都有一个循环
重点:1.accept一个线程;2.读、写、错误另起线程。
注:clientfd%io_threads分配;另起的线程需要创建epoll对象。
概述:监听线程和处理线程分开,每个线程创建一个epoll,loop循环就是这个epoll。

one loop per thread的变种1:one loop per thread+队列+线程池(会有一定的延时)

比较
1.reactor:突然大量的连接或流量涌入,会降低吞吐率。
原因:因为epoll_wait的处理能力有限,大量的连接涌入不行;因为大量的流量涌入会导致阻塞操作,会影响其他的连接处理。redis,redis6.0引入多线程,只是解决协议序列化的问题。
2.one loop per thread ,memcached-KV数据库,数据结构比较简单,加锁简单。

问题3:redis为什么选择reactor而不是用one loop per thread

  1. redis不仅是KV数据库还是数据结构数据库,支持多种数据结构:如string、hash、set、zset、stream、list,加锁非常麻烦。
  2. redis运行过程是一个空间和时间均衡的过程,存储数据的时候是变换的。
    压缩列表(字节数组)
    hash:<=512节点的时候是(节省空间),使用压缩列表;>512节点,使用dict(节省事件)。
    zset:<=128节点的时候,使用压缩列表;>128节点使用跳表。
    set:数量少时用intset,数量多时用dict存储。

3.例子

银行 epoll
门 accept
柜台 网络IO处理,可能包含业务处理

单线程reactor:一个银行 一个柜台
reactor+线程池:一个银行 多个柜台
one loop per thread:多个银行,每个银行一个柜台
one loop per thread+线程池:one loop per thread,每个银行,每个银行多个柜台

4.知识点

阻塞IO和给阻塞IO
1.阻塞在网络线程;
2.IO操作在没有数据到达时是否立即返回;
3.创建fd的时候,决定了是否阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值