协程服务器常问问题
1️⃣ 面经一
- 什么是协程(用户态线程,用户栈,用户态切换)
- 协程和线程相比有什么优势(轻量级、切换快、开销小,用户管理,配合非阻塞
IO更好地实现异步并发) - 更好地实现异步并发是怎么理解的(
IO操作时检测到需要等待缓冲时切换协程) - 线程也可以在等待时切换,协程的优势是什么(用户自己操作)
- 轻量级怎么理解(线程的栈是
MB级别的,协程的栈是KB级别的,线程在内核中切换,协程在用户态中切换) - 怎么设置非阻塞(
fctnl) - 返回EAGAIN是什么意思(缓冲区没准备好)
- 互斥锁怎么实现的(访问锁时如果锁被占用就阻塞,加入阻塞队列,锁被释放时唤醒阻塞队列的中的线程进入就绪队列)
http协议怎么解析的(引入了其他项目的解析器)http是什么格式(消息行 消息头 消息体)N:M的协程调度器是什么(N线程处理M协程)- 如何调度(先来先服务,线程依次去协程队列取任务执行,如果没有任务就执行空闲协程,陷入epoll_wait,有任务时再唤醒空闲线程去执行任务)
- 如果所有线程都不空闲,其他协程是不是就不调度了 (不会立刻调度,要等当前占着线程的协程让出执行权)
- 其他协程就不调度了吗
- 当前框架是协作式 / 非抢占式调度。调度器只能在协程主动让出时切换,不能强行把正在运行的协程“抢下来”。
- 当前框架更适合 IO 密集型,不适合 CPU 密集型
- Go 在这方面做得更完善。Go 从 1.14 开始支持 goroutine 的异步抢占,但那需要安全点、信号和运行时配合,复杂度比我当前项目高很多
- 堆排序时间复杂度(
O(nlogn),建堆后n次下滤) - 快速排序时间复杂度(平均
O(nlogn),最坏O(n2))
2️⃣ 面经二
- 面向对象的三大特性(封装、继承、多态)
new/malloc的区别(性质、原理、大小、失败)- 虚函数表放在哪(常量区)
- 基类和子类的构造和析构顺序(构造先基类后子类,析构相反)
- 讲一下
map的实现?具体细节?(红黑树及其性质) move的实现和原理? (static_cast<std::remove_ref<T> &&>())- 如果不是指针呢?比如
move的是结构体对象,有指针也有int?(基本类型拷贝,对象进行移动) - 进程空间布局(代码段、
data段、bss段、堆、文件映射区、栈) - 段是怎么理解的(内存段,存储代码或者数据)
- 页表是管理什么的(虚拟内存和物理内存映射关系)
- 进程线程协程的区别(从资源和切换上答)
linux下进程和线程的调度有区别吗?(没有区别,都用task_struct表示,看成一个任务进行调度)- 栈和堆有什么区别?(管理方式、空间大小、分配释放、访问效率、生命周期)
- 栈为什么比堆效率高?
- 栈只需移动栈帧,堆需要找合适的内存空间
- 栈帧中的局部变量通常在一段连续区域里,函数执行时访问集中,CPU Cache 容易命中;堆对象经常比较分散,指针跳转多,
cache miss概率更高 - 堆容易产生额外间接访问
- 栈内存和堆内存都要找到内存地址,怎么对比速度?
- 地址计算成本不同
- 栈变量的地址通常是已知规则的,比如 “SP/BP + 偏移量”,编译器很容易生成指令,定位非常直接。
- 堆对象的地址通常先要通过分配器申请得到,再通过指针间接访问。对象位置更随机,寻址链路可能更长。
- 缓存命中率不同
- 栈上的数据通常连续,当前函数频繁访问,
cache更容易命中 - 堆上的对象分布离散,尤其链表、树这类结构,容易
cache miss
- 栈上的数据通常连续,当前函数频繁访问,
- 地址计算成本不同
- CPU 多级缓存设置的目的是什么?(局部性原理,把近期或频繁使用的数据放到更靠近
CPU、速度更快的存储层里) - 那栈和堆,是不是和
CPU多级缓存是类似的?(栈和堆是进程虚拟地址空间中的内存组织方式;L1/L2/L3Cache是CPU的硬件存储层次) - 并发和并行有什么区别(多个任务切换执行/多个任务同时执行)
- 阻塞
IO和非阻塞IO区别(等待缓冲时是阻塞还是返回错误) - 动态链接和静态链接的区别(运行时链接库/编译时复制库)
epoll的实现(将需要监听的fd保存在内核中的一个红黑树中,监听到事件时返回对应的fd)epoll与select的区别(内核红黑树、异步回调、只返回有事件fd)- 边缘触发和水平触发(事件发生后只通知一次/只要有数据没读写完就再次通知)
- 怎么判断事件有没有读写完(读写时会给定数据的真实长度,返回值是真正读写成功的数据长度)
- 没读写完会产生什么错误码(
EAGAIN) TCP四次挥手- 客户端
timewait过多怎么办- 产生原因:短连接太多,客户端经常主动关闭连接
- 解决办法:
- 减少短链接,替换成长连接;
- 让服务端主动断开;
- 扩大客户端可用端口资源
ip_local_port_range; - 复用
timewait对应的socket,内核参数:net.ipv4.tcp_tw_reuse; - 调整系统所能容纳的
timewait数量,内核参数:net.ipv4.tcp_max_tw_buckets;
- 一个数据包从网卡到应用层的收包过程
- 这个过程中软中断是那一部分?
- 应用层到内核缓冲区读数据会有软中断吗
- 对这个过程中的
reuseport有了解吗?reuseport指的是多个socket可以同时bind到同一个IP:Port,数据包一路到传输层后,内核根据四元组找到目标socket;如果目标端口对应的不是单个socket,而是一组开启了SO_REUSEPORT的socket,那么内核会在这组socket之间选一个来接收这个连接或数据包。
reuseaddr了解过吗?reuseaddr放宽bind()时对本地地址的校验规则,允许更快重用本地地址/端口- 最常见的使用场景是:服务端重启。
- 内核在什么情况会发送
RST(内核认为 “这个报文不属于当前连接” 或者 “本端要直接中止连接” 时) - 怎么判断是错误的连接(序列号和确认号)
HTTPS完整的握手流程(RSA握手)- 具体有哪几种算法(
RSA和ECDHE) HTTP Session复用HTTPS的Session 复用其实是TLS会话恢复。常见两种:Session ID和Session Ticket。Session ID是服务端保存会话,客户端下次带ID来命中缓存恢复;Session Ticket是服务端把会话状态加密后发给客户端保存,下次客户端带回来恢复,服务端更省状态,也更适合负载均衡。
HTTP长连接(发送一次请求和响应后不断开)QUIC了解过吗?(解决TCP队头阻塞、更快连接,端口迁移)- 为什么
QUIC连接更快? - 还了解
QUIC的其他的知识吗? - 有性能测试吗,有对比其他服务器比如
Nginx吗? - 服务器接收和响应的数据流是怎么样的,从
accept开始(accept是单独一个协程,有新连接就建一个新协程) - 为什么用协程(切换开销小,配合非阻塞实现异步)
- 定时器用什么实现(
set) - 支持主线程调度任务减少开销是什么意思(调度器线程执行完调度任务后也用来执行任务)
参考来源
- 作者:_陈顺
- 链接:https://www.nowcoder.com/discuss/620388024718774272?sourceSSR=users
- 来源:牛客网
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GYu的妙妙屋!
