面试必问:六大高并发模型!

网络编程是Linux C/C++的面试重点,这次我就来聊聊进程间通信和线程同步的问题,可以参看《unix高级环境编程》,希望帮助到大家。

值得一读:
1、https://www.nowcoder.com/discuss/193598?source_id=profile_create&channel=2002(Linux C/C++ 学习路线,已拿 BAT offer!)
2、https://www.nowcoder.com/discuss/225624?source_id=profile_create&channel=2002(我凭着这份简历,拿下了 BAT offer)

关于如何写简历,以及我的简历模板(好好看看上面推荐的文章),或者实习、秋招有什么问题,都可以私信找我,很愿意帮助到大家。

上次分享了进程间通信和线程同步的问题,这次就来说说高并发的一些事情,这些都是网络编程的重点,也是面试常问的一些,我将自己整理的一些,分享给大家。


我在校期间走的是Linux C/C++方向,提供的都是Linux下面的编程模型,希望对需要的人有帮助

正文


六大高并发模型


多进程、多线程、线程池、select、poll、epoll

1、多进程并发编程


利用的是TCP协议

核心思想:一个服务器和客户端进行并发访问,每个客户端都有自己的套接字描述符,在connect()、send()、recv()时发送对应的socket文件描述符(就知道是哪个客户端跟服务器进行通信,谁发的数据,将数据发给谁就一清二楚);然后服务器给每一个客户端都fork()一个子进程,形成一对一的访问机制;当一个客户端连接关闭时,的退出当前子进程

缺点:a、启动和关闭子进程带来很大的开销;b、系统最多只能产生512个进程,也就是说最多只有512个客户,形成不了处理大型访问的情形

多线程并发编程

利用的是TCP协议

每到来一个客户端,就创建一个线程,通过其中的函数指针来处理业务;通过connect()来进行连接,发送其客户端的套接字;是一对一的服务;当一个客户端连接关闭时,的退出当前子线程

缺点:多线程网络服务也存在线程的动态申请与释放,还是有一定的开销,若存在大量用户在线,很可能带来线程间切换开销

2、线程池并发编程


针对多线程网络服务模式的一些不足之处而提出的改进模式

其基本理念是:先创建一批资源,当有用户到来时,直接分配以创建好的资源,它的主要目的是减少系统在频繁创建资源时的开销

实现原理:主服务线程创建既定数量的服务线程,当有客户端到来时,则从线程池中找出空闲的服务线程,为其服务,服务完毕后,线程不进行释放,重新放回线程池;若当前线程池已满,则将当前的客户端加入等待队列

3、怎么实现线程池


typedef struct PoolStruct{
int sockConn;   //接收客户端的套接字,通过accept()函数的返回;
THREAD_TAG flag;   //连接的状态,此时的繁忙/空闲
}PoolStruct;

将空闲、繁忙状态写为枚举类型;

typedef enum{IDEL, BUSY} THREAD_TAG;
在客户端连接前,先创建一批线程资源

创建线程池数组,首先遍历一遍数组,将套接字初始化为:0,设置为:空闲状态

每连接一个客户端accept(),然后遍历线程池数组,找到空闲状态的,将当前客户端的套接字赋值过去(目的:记录当前服务器是与哪个客户端进行通信),并且将当前状态置为繁忙;用该线程为用户提供一对一的服务

线程结束后,将其不回收,在次放入线程池,将套接字设为0,状态设为空闲,

若线程池中的线程都在繁忙,用户进入等待状态,将其用队列(accept()函数的返回值,接收客户端的套接字)存储起来,先入先出,每当下一个客户端连接该服务器时,调用该函数,先看等待队列中有没有等待的用户,有,直接加入等待队列,没有,都要遍历一遍线程池数组,看看何时有空闲的线程资源;此时有空闲资源,先分配给队列中的用户,队列中没有,则分配给该用户

分析总结:

(1)、其优点:性能高效

(2)、可能存在的问题:新用户如果在等待队列里耗时过长,会影响用户体验

针对此问题,改进方案如下:

a、动态创建新的服务线程,服务结束后,该线程加入线程池,这种改进的好处是,用户体验得到提升,潜在问题是,在长时间,大规模的并发用户状态下,线程会产生很多,最终会因为资源消耗过多,系统退出

b、增加一个线程资源回收机制,当线程池的规模达到一定程度或满足某种既定规则时,会主动杀死一些线程,以达到系统稳定和用户体验之间折中。

当有客户端来,有2种做法,i>、创建线程为其服务;ii>、加入等待队列;这2种都不太合适,采用折中法,有一个上限值,即就是规定一个创建线程的最大数,当来一个用户,还没达到线程最大数时,为其创建线程,若达到了,则加入等待队列

对线程资源的回收:i>、立马回收,ii>、暂时不回收;当空闲的线程数达到某一下限值时,此时再将线程回收

Linux I/O多路复用,都是针对高并发的情况下,创建一个线程可以为多个客户服务的一种模式;同一个时刻,只能为一个客户服务(作用排队)

4、select()


select的调用在FD_ZERO()和FD_SET()之后调用

5、poll()


poll()的调用时机和select()一样,在监听之后

6、epoll()


epoll的调用顺序:socket---->bind----->listen----->epoll_create----->增加事件(epoll_ctl())>epoll_wait----->accept(建立连接)---->读/写(在读写中删除和修改事件-->通过epoll_ctl())

分模块的执行每一块代码

epoll_create();创建一个文件描述符,来唯一的标识内核中创建的事件表(用户关心的文件描述符就放在内核事件表中)

epoll_ctl():往事件表上注册/修改/删除事件

epoll_wait():返回就绪的文件描述符的个数,如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中

该events数组只输出检测到的就绪事件

比较


1、epoll的工作模式:


LT(电平触发):当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件,这样,当下一次调用epoll_wait时,还会再次向应用程序告知此事件,直到该事件被处理

ET(边沿触发):当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件

所以:ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高

EPOLLONESHOT事件:针对使用ET模式还是可能被触发多次,只有在epoll_ctl函数的文件描述符上注册EPOLLONESHOT事件,此时只触发一次

2、三组I/O复用函数的比较


都能同时监听多个文件描述符,直到一个或多个文件描述符上有事件发生时返回,返回值是就绪的文件描述符的数量

事件集:

select:参数fd_set,没有将文件描述符和事件绑定;其仅仅是一个文件描述符集合

poll:参数pollfd,将文件描述符和事件都定义其中,任何事件都被统一处理,使得编程接口简洁

select和poll每次调用都返回整个用户注册的事件集合(就绪的和未就绪的);索引就绪文件描述符的时间复杂度:O(n)

epoll:在内核中维护一个事件表,通过epoll_ctl往其中添加、删除、修改事件,epoll_wait都直接从内核事件表中取得用户注册事件,而不用反复从用户空间读入事件,索引就绪文件描述符的时间复杂度:O(1)

最大支持文件数:

select :1024

poll、epoll:系统打开最大文件描述符的数目;65535

工作模式:

select和poll只能工作在相对低效的LT模式,

epoll可以工作在ET高效模式,epoll还支持EPOLLONESHOT事件,该事件进一步减少事件被触发次数;

具体实现:

select和poll:采用轮询方式,每次调用都要扫描整个注册文件描述符

epoll_wait:无需轮询整个文件描述符,根据下标直接定位(events数组中即就绪文件),算法复杂度:O(1)

当活动连接比较多的时候,epoll_wait的效率未必比select和poll高,因为此时回调函数被触发的过于频繁,epoll_wait适用于连接数量多,但活动连接少的情况

3、select()和poll()模式主要有3个缺点


(1)、select()的fd有最大的限制,1024/2048,并发数被限制了,poll的并发数与epoll相同

(2)、内存拷贝问题,每次都需要将fd集合从用户态拷贝到内核态,开销较大,内存拷贝方法内核把fd通知给用户空间

(3)、select模式每次都会线性的扫描从内核传递进来的fd集合,效率比较低

引出了epoll模型,epoll模型是Linux独有的I/O复用模型,做了很大的改进

(1)、epoll模型没有最大并发连接的限制,所可以并发的数目很系统内存有很大的关系

(2)、epoll模型使用了"共享内存",不存在内存拷贝的问题了

(3)、epoll模型只管连接"就绪"的,而跟连接总数无关,也就不存在遍历了

4、epoll模型的高效关键在于数据结构的设计


typedef union epoll_data {
 void        *ptr;
 int          fd;  //一般情况下,都用的是这个文件描述符
 uint32_t     u32;
 uint64_t     u64;
} epoll_data_t;

struct epoll_event {
   uint32_t     events;      /* Epoll events */
   epoll_data_t data;        /* User data variable */
};

epoll_data是一个union结构体,保存了:fd,指针,利用其就可以直接定位目标了

epoll_wait()

5、对epoll_wait()函数的核心理解


(1)、返回值:事件表中就绪客户端的个数;等待队列

(2)、参数events:将事件表中的就绪客户端的信息放到了events数组中。

epoll()的核心思想

就是创建一个内核事件表,存放所监听客户端的套接字和当前的事件,在利用epoll_wait()函数查找就绪的套接字,最后经过增加、删除、修改利用epoll_ctl()函数进行
#字节跳动2021秋招开始了##字节跳动##腾讯##笔试题目#
全部评论
日常工作较忙,很少上牛客。 1、对于找实习时,被卡简历,需要参考我简历模板的。 2、对于大厂实习期间的疑惑,暑期实习打法相关、秋招面试相关、简历修改相关,想了解更清楚的同学。 都可以加我微信联系我(要简历或者咨询实习):18519336960,备注:牛友
3 回复
分享
发布于 2020-07-03 21:16
前排支持!!!!
1 回复
分享
发布于 2020-07-03 21:37
博乐游戏
校招火热招聘中
官网直投
哈哈哈,来晚了,没占到一楼,支持!
1 回复
分享
发布于 2020-07-04 02:11
支持。六大高并发模型分析得很到位,公众号中都是精华的总结,推荐~
1 回复
分享
发布于 2020-07-04 08:29
支持!!!
点赞 回复
分享
发布于 2020-07-03 22:12
支持~
点赞 回复
分享
发布于 2020-07-03 22:43

相关推荐

16 120 评论
分享
牛客网
牛客企业服务