在讲解复用并发模型之前,先补齐一些知识:
设想一个场景,你今天想洗衣服,但是没有洗衣粉,于是你让快递小哥送来,那在送的这段时间,如果你干了别的活,洗衣服这件事情就被阻塞了,等待快递小哥送来洗衣粉后,唤醒洗衣服的事件。
阻塞:将CPU资源让出,等待事件唤醒。
如果你没有干别的,而是在不停的给快递小哥打电话询问到哪里了,那末这个行为就叫,非阻塞忙轮询。
非阻塞忙轮询:继续占用CPU资源,持续判断是否有事件。
如果你同时买了多个快递,都在派送,但是此时只有一个电话,那末其他的小哥要依次拨打,否则会提示占线。
阻塞等待存在问题:同时只能处理一个IO请求,其余的IO请求会阻塞。
如何解决呢,你可以将收快递业务托管给一个驿站,这个驿站收到快递会通知你。一种是通知你到了,但是不通知你到的是哪个,需要你自己查询。 另一种是通知你哪个到了,不需要再去查询
解决方式:多路IO复用
select/poll:内核拷贝到用户态的是全部的文件描述符集合,还需要遍历得到触发的那个。 文件描述有上限,默认1024。
epoll:只在linux系统中才有的。 内核拷贝到用户态的是被触发的文件描述符,不需要遍历可以直接去处理。存放的文件描述符更多了。
前瞻知识讲完了,下面介绍公司级多路IO复用并发模型
单线程IO多路复用 + 多线程IO多路复用模型:
流程大概是这样的 服务端起来时,开辟固定数量的线程,主线程会创建listenFd后,并进行绑定和监听。等待client向server发送conn连接请求。
IO多路复用发现监听描述符触发后,会调用accept进行连接。三次握手连接成功后,得到了一个新的连接描述符connFd。此时会通过轮询或者常用负载方式,将连接描述符交给某一个子线程。此时主线程又会返回到IO多路复用监听状态,继续监听listenFd。
子线程拿到connFd,会放入子线程内部的监听IO集合中。 client发起 read write后,子线程的IO集合监听到connFd触发,也会开始 read write。双方读写数据成功。
优点:
- 主线程,只监听连接操作,不做读写操作,分散读写到多个子线程来完成。增加同一时间读写的并行通道。并行数量为子线程的数量。
- server同时监听ConnFd套接字数量N倍增加
建议子线程数量和CPU核心数一致,会降低CPU切换的频率。CPU切换线程是有成本的,每个线程绑定一个CPU,可以让CPU运行最大化。省掉切换的成本
缺点:
每一个子线程内同时读写的只能有一个,其余的会被延迟处理。等待读写完成后,回到多路IO复用方法后,才能再次执行。