• Home
  • 写文
  • 关于
    • jlweb Blog photo

      jlweb Blog

      occupied with moon theme of jelly

    • 详情
    • Github
    • Steam
  • 文章
    • 所有文章
    • 所有标签
  • 项目
  • 主站
search clear

字节

字节

调用fork函数发生了什么

image.png

copy on Write
Nginx网络模型及其惊群问题

其实,在linux2.6内核上,accept系统调用已经不存在惊群了(至少我在2.6.18内核版本上已经不存在)。大家可以写个简单的程序试下,在父进程中bind,listen,然后fork出子进程,所有的子进程都accept这个监听句柄。这样,当新连接过来时,大家会发现,仅有一个子进程返回新建的连接,其他子进程继续休眠在accept调用上,没有被唤醒。 但是很不幸,通常我们的程序没那么简单,不会愿意阻塞在accept调用上,我们还有许多其他网络读写事件要处理,linux下我们爱用epoll解决非阻塞socket。所以,即使accept调用没有惊群了,我们也还得处理惊群这事,因为epoll有这问题。上面说的测试程序,如果我们在子进程内不是阻塞调用accept,而是用epoll_wait,就会发现,新连接过来时,多个子进程都会在epoll_wait后被唤醒! “惊群”,看看nginx是怎么解决它的-CSDN博客 简单一句话 概况epoll惊群解决:同一时刻只允许一个nginx worker在自己的epoll中处理监听句柄

nginx采用的是 多进程-单reactor 模型,因为每个连接都是处理的无状态数据,故可以通过多进程实现,多进程之间共享唯一的一个epollfd,以实现多个进程可以并行处理事件。 image.png 【陈年往事】内核2.6以前,accept还存在惊群问题,即如果有连接到来,多个进程的epoll_wait都能监测到,这样多个进程都处理了该相同的连接,这是有问题的,所以nginx采用了文件锁的方式,针对accept事件加入了 accpet_mutex,并为accpt事件单独设置一个队列,和read/write事件的任务队列进行了分离; image.png

tcp拥塞控制算法,BBR思路区别

Reno算法所包含的慢启动、拥塞避免和快速重传、快速恢复机制,是现有的众多基于丢包的拥塞控制算法的基础。发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量和慢开始门限ssthresh状态变量; TCP BBR 不仅适合TCP场景,同时QUIC也使用了BBR作为拥塞控制算法 根据带宽和RTT延时来不断动态探索寻找合适的发送速率和发送量,TCP BBR算法是一种主动式机制,简单来说BBR算法不再基于丢包判断并且也不再使用AIMD线性增乘性减策略来维护拥塞窗口,而是分别采样估计极大带宽和极小延时,并用二者乘积作为发送窗口,并且BBR引入了Pacing Rate限制数据发送速率,配合cwnd使用来降低冲击。

五大I/O模型+epoll

深入理解Linux的五种IO模型

虾皮子

补充押题

GDB如何调试多线程
  1. gcc 编译时-g 开启后才会带上gdb调试信息
  2. gdb 启动程序/附加进程
  3. thread 切换线程
  4. info threads 线程信息汇总
  5. scheduler-locking 锁定gdb只跟踪特定线程
  6. b 函数名/行号 设置断点
  7. next/step/finish/continue 控制流(步过/步入/返回调用处/继续执行)
  8. bt 打开backtrace调用堆栈看
  9. watch 监控变量
  10. print打印变量

扩展:cgdb图形化辅助,可显示源码布局 gdb-gui:浏览器调试

#pragma pack(8/4) 默认值及字节对齐规则

在字节对齐时候,默认思想按照自然大小进行对齐,但是VC里面的pagrama不管有没有定义都会有一个默认值,可以认为32位里是4,64位下默认是8,设置时超过默认平台值会无效,且需要为1,2,4,8这种值; 这时候,我们原来字节对齐规则: 1.成员起始地址是自身大小整倍数 2.struct整体大小对齐是最大成员大小的整倍数 收到对齐值pack的设置影响,大小基准(标记处为基准)会变为 min_num1 = min(pack_value, sizeof(type) ); min_num2 = min(pack_value, max(sizeof(types …) ) ); 即: 1.成员起始地址是min_num1 整倍数 2.struct整体大小对齐是min_num2的整倍数

#pragma pack(1)的实际用途

[p’ræɡmə] 设定 特征:内存紧凑挨着,等同于字节对齐取消;可用于网络包传输前的处理,提高传输效率,坏处是可能CPU访存两次才能读取到一个变量的值; 参考TCP-header声明: image.png 通过合理的人为布局设计,完全避免了数据成员cpu两次访问再组合的问题,这时候就可以大胆用#pragma pack(1),避免不同编译环境下的再排列;

三大进程状态的切换时机总结

image.png

为什么派生类先析构,和构造顺序相反

因为基类成员先压栈,再把追加的派生类成员压栈,从栈弹出角度看,应该是后构造的派生类数据先析构弹出; 或者从应用分析,如果基类先析构了,派生类析构函数访问了基类成员变量进行判定逻辑之类处理岂不是访问到已释放的空间了?所以应该是先自身析构函数执行末尾再调用基类的析构。

为什么析构函数需要设置为virtual

准确的说应该是在多态应用场景下,基类中析构函数需要为虚构函数; 派生类是否是virtual取决于基类,自己声明与否用处不大,只是提示开发者; 【注意】 无论是否virtual,子类析构函数都将执行末尾调用基类的析构;

说一下map查找一个元素的过程find

map find/count源码分析-CSDN博客

众所周知,如果一个数据结构想作为map的key,则必须重载 operator < 否则编译将会报错。但是operator == 则是不需要的。那么当查找某个key是否存在的时候,map内部是怎么实现的呢?

和lower_bound一致逻辑,和count不同,count会单独比较是否相等,看看count源码: image.png 再看看rb-tree查找逻辑(lower_bound): image.png 总结下:基于默认的less我们探讨下查找逻辑 image.png 可以看到比较思路是,如果key大于当前节点key值就去右子树重新查询; 如果是key小于等于当前Node,就设置结果result=当前Node的迭代器,然后继续去左子树再查询; 最终到了叶子节点,结果更新完成,找到了第一个大于等于查询key的节点迭代器位置并返回,而find基于lower_bound包装了一层 return (auto it =lower_bound(_Keyval) != end() && _Keyval == *it->_Keyval ) ? it : end(); 意思就是lower_bound查到了非end()节点,同时keyvalue等于传入目标的keyval时候就说明找到了,返回it,否则返回end();

对于读写锁,如果写进程很耗时,而读进程比较多如何设计。

降低写进程拿锁权重,公平锁倾斜,优先拿读锁,让写饥饿; 引入拆分+公平锁:耗时长的写操作,while逐段写入,间隔期间放弃写锁,让读锁间隙获取; 通过公平锁策略调度 ,让写锁能够不饥饿后续持续写入;

  • rwlock由于区分读锁和写锁,每次加锁时都要做额外的逻辑处理(如区分读锁和写锁、避免写锁“饥饿”等等),单纯从性能上来讲是要低于更为简单的mutex的;
  • 但是,rwlock由于读锁可重入,所以实际上是提升了并行性,在读多写少的情况下可以降低时延。