文章目录
- 后端太卷,建议往嵌入式,qt,测试,音视频,C++一些细分领域投简历。
- 有任何疑问评论区聊,我看到了回复
- C++面试/笔试准备,资料汇总
- 自我介绍
- 项目实习尽可能有
- 1.编程语言:
- 一.熟悉C++语言,熟悉std::string的底层实现。
- string的底层实现(写时复制技术)
- 1、 引用&指针
- 野指针
- 2、C++中的继承,多态,封装,
- 3、 什么是多态,什么是虚函数,实现原理
- 4、C++中struct与class的区别
- 5、 new和malloc区别
- 5、 四种强制类型转换及应用场景
- 6、 ++i和i++
- 7、浅拷贝和深拷贝
- 8、C++中的内存分区,
- 二.熟悉STL六大组件的使用,了解各容器的底层实现。
- 9、常见容器性质总结?
- 三.掌握面向对象的编程思想,理解类与类之间的关系,了解面向对象的设计原则。
- 面向对象设计原则
- 四.掌握C++11新特性。(移动语义,智能指针)
- 8、C++提供了哪几种智能指针,其各自的特点是什么?
- 2.数据结构和算法:
- 掌握常用的数据结构,例如链表、栈、队列、二叉树、哈希表。了解红黑树、B树、B+树等。
- 掌握八大排序算法。
- #十大排序中的非稳定排序
- 3.网络编程:
- 熟悉Linux下的Socket编程、熟悉TCP协议,TCP报文头,掌握网络IO复用模型select/epoll。
- 网络socket本地socket区别
- 五种I/O模型
- 1、OSI七层协议
- 2、工业标准 4层或5层协议
- 3、数据在网络中发送时的情况
- 4、传输层TCP协议
- 5、TCP建立连接时会采用三次握手,为什么?
- 6、2次握手,可以不?
- 8、TCP连接断开时,要采用四次挥手,为什么?
- 9、服务器是否可以主动断开连接呢?
- 10、 为什么需要TIME_WAIT状态?
- 11、删除TIME_WAIT状态,是否可行?
- 12、Socket![image-20230302080040675](https://img-blog.csdnimg.cn/img_convert/cdb8650c8a8287837033a23e2eb5113a.png)
- 13、epoll 与select的对比
- 14、水平触发 vs 边缘触发
- 15、发送数据时,若一次没有发送完成,需要执行滑动窗口机制
- 16、mmap
- 掌握并发服务器模型Reactor。
- 掌握Http协议。
- 17、HTTP全称超文本传输协议。
- 18、URI由哪些部分组成?
- 19、HTTP请求报文和响应报文由哪些部分组成?
- 20、HTTP常用方法有哪些?
- 21、HTTP常用状态码有哪些?
- 22、form-data的boundary起什么作用,有什么限制?
- 23、 简要说明一下HTTPS的握手阶段流程。
- 24、知道DNS是什么?
- 25、HTTPS和HTTP的区别
- 26、在浏览器中输入url地址后显示主页的过程?
- 4.操作系统:
- 一、熟悉使用linux(ubuntu18.04)。
- 什么是Linux?
- Linux和Unix有什么区别?
- 什么是Shell?
- 什么是文件系统?
- 什么是进程?
- Linux中如何查看进程?
- Linux中如何安装软件包?
- Linux中如何查看系统日志?
- Linux中如何设置文件权限?
- Linux中如何设置环境变量?
- Linux中如何查看网络配置?
- Linux中如何查看硬件信息?
- Linux中如何查看磁盘使用情况?
- Linux中如何查看系统负载?
- Linux中如何创建用户?
- Linux中如何修改用户密码?
- Linux中如何安装服务?
- Linux中如何启动和停止服务?
- Linux中如何查看系统版本?
- Linux中如何查看当前用户?
- Linux中如何设置定时任务?
- Linux中如何远程登录?
- Linux中如何设置防火墙?
- Linux中如何查看当前系统使用的内存和CPU情况?
- Linux中如何查看当前系统的IP地址?
- Linux中如何设置系统时间?
- Linux中如何查看系统中的进程数量?
- Linux中如何查看系统中安装的软件包?
- Linux中如何查看系统中的网络连接?
- Linux中如何安装和配置Nginx Web服务器?
- 二、熟悉进程间通信,多线程编程、文件读写(buffer I/O,unbuffer I/O ,mmap )。
- 内存虚拟化
- 1.进程通信的方式:
- 2.进程调度算法:
- 3.分段、分页、段页式
- 4.什么是孤儿进程,什么是僵尸进程,他们对操作系统有什么影响?
- 5.进程的结束方式有哪些?return和exit有什么区别?
- 7、进程、进程组、会话和控制终端的关系是什么样的?
- 8、守护进程和后台进程组的区别是什么?
- 9、无名管道和命名管道有哪些特点和区别?
- 10、什么是线程?线程和进程有哪些区别?
- 11、线程的标识
- 线程的创建
- 12、线程的清理函数什么时候会执行?什么时候不会执行?
- 13、 为什么`pthread_cleanup_push`和`pthread_cleanup_pop`需要成对出现?
- 14、使用条件变量的使用为什么一定要加锁?
- 生产者与消费者问题,
- 互斥锁
- 条件变量
- 线程池
- 同一进程下的线程可以共享
- 三、熟悉进程池、线程池的实现。
- 1、进程池:
- 四、简单了解分布式系统,消息队列。
- 线程池的实现
- oo_threadpool
- 5.设计模式:
- 方式一: 嵌套类 + 静态对象
- 方式二: 自动调用destroy方法,就可以回收单例对象
- 方式三: pthead_once + atexit,就可以回收单例对象(有环境限制,只能在linux环境下,实现了多线程安全)
- 类与类之间的关系—继承(泛化)
- 类与类之间的关系—关联
- 类与类之间的关系—聚合
- 类与类之间的关系—组合
- 类与类之间的关系—依赖
- 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结
- 工厂模式
- 代理模式(Proxy Pattern)【结构型模式】
- 观察者模式(Observer Pattern)【行为型模式】
- 6.数据库:
- 1、事务的四大基本特征是什么?
- 2、并发产生的四大基本问题是什么?
- 4、B树和B+树的区别?
- 5、什么是最左前缀原则?
- 6、MyISAM与InnoDB各自有什么特点?
- 5、MyISAM索引与InnoDB索引的区别?
- 6、临时表与存储引擎是memory的表有什么区别?
- 掌握Redis。
- 1、 什么是Redis?有哪些有优缺点?
- 2、为什么要用 Redis /为什么要用缓存
- 3、 Redis为什么这么快
- 4、Redis有哪些数据类型
- 5、什么是Redis持久化?
- 6、Redis 的持久化机制是什么?各自的优缺点?
- 7、 Redis key的过期时间和永久有效分别怎么设置?
- 8、Redis事务的概念
- 9、Redis事务的三个阶段
- 10、Redis事务相关命令和事务的特征?
- 11、缓存穿透是什么?如何解决?
- 12、缓存击穿是什么?有什么解决方案?
- 13、缓存雪崩的概念?如何应对?
- 7.常用工具:
- 面试雷区
- 一、“我没有其他问题要问了......”❌
- 二、“期望薪资方面我没有什么想法...”❌
- 您好,我在上家公司实习/工作的时候到手的薪资是xxx,结合我的工作经验和个人诉求,我的期望薪资是xxx,根据目前的行业前景和市场行情,我觉得我的要求是合适的,以上就是我个人的薪资期望,您这边觉得怎样呢?
- 三、“我对该岗位没有什么经验,但我会认真去学习哒...”❌
- github
后端太卷,建议往嵌入式,qt,测试,音视频,C++一些细分领域投简历。
有任何疑问评论区聊,我看到了回复
C++面试/笔试准备,资料汇总
自我介绍
面试官 您好,我是XXX,就读于XXX大学XXX专业,很荣幸有机会参加此次面试,我这次面试的是linux助理开发工程 师岗位,我们应届生呢经验不太多,但是我去年去XX实习了4个月,具有一定的实践经验,我之前研究了贵公司的linux助理开发工程 师岗位需求
第一,我在开发环境上,熟悉linux,windows开发环境,有自己配置的linux开发环境,使用过阿里云的对象存储
第二,熟悉C++,STL;
第三,熟悉网络编程,掌握常用算法,最近在参加一期60天的算法训练营,坚持14天了吧。边练边写边写写博客,简历上有博客连接。github上记录了自己的学习经历,以及一个面向对象和基于对象的线程池代码,目前还在消化吸收。
汽车全液晶仪表盘HMI显示工具,kanzi
shader就是专门用来渲染3D图形的一种技术
第四,有实际项目经验是linux下的两个项目。熟悉git提交代码,
我的毕设就打算写一个基于workflow的小型私有云系统。虽然还未完工,但最近几天实现了内网穿透,可以在网络上访问我的项目。
我的责任心很强,目标导向,做事情都会全力以赴。
爱好:XXXXX
职业发展:高级软件工程师,系统分析师,系统架构师。
特别期待加入您的团队。
项目实习尽可能有
必须有自己找个练练!!!,要资料的评论区聊
1.编程语言:
一.熟悉C++语言,熟悉std::string的底层实现。
string的底层实现(写时复制技术)
1、 引用&指针
- 指针是一个变量,存储的是一个地址,引用跟原来的变量实质上是同一个东西,是原变量的别名
- 指针可以有多级,引用只有一级
- 指针可以为空,引用不能为NULL且在定义时必须初始化
- 指针在初始化后可以改变指向,而引用在初始化之后不可再改变
- sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小
- 当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以。
- 引用本质是一个指针,同样会占4字节内存;指针是具体变量,需要占用存储空间(,具体情况还要具体分析)。
- 引用在声明时必须初始化为另一变量,一旦出现必须为typename refname &varname形式;指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。
- 引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用);指针变量可以重新指向别的变量。
- 不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。
使用:
经常用指针指向结构体或某个类,以免重复构造,消耗
野指针
产生野指针:
1.指针变量未初始化或随便赋值
2.指针释放后未置空
3.指针操作超出了变量的操作域(于是指向内存都是随机的)
避免产生
1.指针初始化
2.释放后置空
2、C++中的继承,多态,封装,
大特性:继承、封装和多态
(1)继承
让某种类型对象获得另一个类型对象的属性和方法。
它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展
常见的继承有三种方式:
- 实现继承:指使用基类的属性和方法而无需额外编码的能力
- 接口继承:指仅使用属性和方法的名称、但是子类必须提供实现的能力
- 可视继承:指子窗体(类)使用基窗体(类)的外观和实现代码的能力(C++里好像不怎么用)
例如,将人定义为一个抽象类,拥有姓名、性别、年龄等公共属性,吃饭、睡觉、走路等公共方法,在定义一个具体的人时,就可以继承这个抽象类,既保留了公共属性和方法,也可以在此基础上扩展跳舞、唱歌等特有方法
(2)封装
数据和代码捆绑在一起,避免外界干扰和不确定性访问。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏,例如:将公共的数据或方法使用public修饰,而不希望被访问的数据或方法采用private修饰。
(3)多态
同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为**(重载实现编译时多态,虚函数实现运行时多态)**。
多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单一句话:允许将子类类型的指针赋值给父类类型的指针
实现多态有二种方式:覆盖(override),重载(overload)。
覆盖:是指子类重新定义父类的虚函数的做法。
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。例如:基类是一个抽象对象——人,那教师、运动员也是人,而使用这个抽象对象既可以表示教师、也可以表示运动员
3、 什么是多态,什么是虚函数,实现原理
- 什么叫多态?
客观现实世界:不同的对象,面对同一个指令(消息),产生不同的行为。
- 多态的分类:
静态多态:编译时可以确定下来,函数重载、运算符重载、函数模板
动态多态:运行时可以确定下来,C++采用虚函数
虚函数的定义
在一个成员函数的前面,加上virtual关键字,该函数就成为虚函数
虚函数实现原理: 当类中定义了一个虚函数时,其对象的存储布局开始位置会
多一个虚函数指针,该虚函数指针指向一张虚函数表
虚函数如何实现动态多态特性的? 虚函数被激活的条件有?
答:1. 基类定义虚函数 2. 派生类覆盖(重定义)该虚函数 3. 创建派生类对象 4. 基类指针(引用)指向(绑定)派生类对象 5. 通过基类指针(引用)调用虚函数
重载(
overload):发生在同一个类中,当函数名称相同时,函数参数类型、顺序、个数不同
隐藏(
oversee):发生在父子类之间,函数名称相同时,就构成隐藏
覆盖(override):发生在父子类之间,基类与派生类中同时定义相同的虚函数****,
**覆盖的是虚函数表中的入口地址,**并不是覆盖函数本身
4、C++中struct与class的区别
一、本身成员的访问级别不同
最本质的区别:结构体的成员和成员函数在默认情况下的访问级别是公有的(public),类的成员和成员函数在默认情况下的访问级别是私有的(private)。
注意: 二者的访问方式都是 对象名+点(对象成员选择运算符)+访问对象成员
- struct的用法:无需加public,struct中成员可直接在结构体外被访用(默认public)
- class的用法:必须加public,否则默认访问权限是private,使得类中成员不能直接在类外被访问
5、 new和malloc区别
相同点
- 都可用于内存的动态申请和释放
不同点
- 前者是C++运算符,后者是C/C++语言标准库函数
- new自动计算要分配的空间大小,malloc需要手工计算
- new是类型安全的,malloc不是。例如:
int *p = new float[2]; //编译错误
int *p = (int*)malloc(2 * sizeof(double));//编译无错误
-
new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用
-
后者需要库文件支持,前者不用
-
new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象
malloc 函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。 调用 malloc()函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内存块。(如果没有搜索到,那么就会用sbrk()才推进brk指针来申请内存空间)。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的那块(如果有的话)返回到连接表上。 调用 free 函数时,它将用户释放的内存块连接到空闲链表上。 到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的片段了。于是,malloc()函数请求延时,并开始在空闲链表上检查各内存片段,对它们进行内存整理,将相邻的小空闲块合并成较大的内存块。
搜索空闲块最常见的算法有:首次适配,下一次适配,最佳适配。 (其实就是操作系统中动态分区分配的算法)
首次适配:第一次找到足够大的内存块就分配,这种方法会产生很多的内存碎片。
下一次适配:也就是说等第二次找到足够大的内存块就分配,这样会产生比较少的内存碎片。
最佳适配:对堆进行彻底的搜索,从头开始,遍历所有块,使用数据区大小大于size且差值最小的块作为此次分配的块。
free只是将内存管理块设置为可用
5、 四种强制类型转换及应用场景
static_cast:void* 向其他类型进行转换
const_cast: 去除常量属性,函数传参时使用
dynamic_cast:基类与派生类之间使用
reinterpret_cast:可以在任意类型间转换
6、 ++i和i++
但是性能是不同的。. 在大量数据的时候++i的性能要比i++的性能好原因:. i++由于是在使用当前值之后再+1,所以需要一个临时的变量来转存。. 而++i则是在直接+1,省去了对内存的操作的环节,相对而言能够提高性能.
7、浅拷贝和深拷贝
- 浅拷贝 浅拷贝又称为值拷贝,将源对象的值拷贝到目标对象中,如果对象中有某个成员是指针类型数据,并且是在堆区创建,则使用浅拷贝仅仅拷贝的是这个指针变量的值,也就是在目标对象中该指针类型数据和源对象中的该成员指向的是同一块堆空间。这样会带来一个问题,就是在析构函数中释放该堆区数据,会被释放多次。默认的拷贝构造函数和默认的赋值运算符重载函数都是浅拷贝。
- 深拷贝 深拷贝在拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样指针成员就指向了不同的内存位置。并且里面的内容是一样的,这样不但达到了拷贝的目的,还不会出现问题,两个对象先后去调用析构函数,分别释放自己指针成员所指向的内存。即为每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。 3. 拷贝构造函数和赋值运算符重载函数就会实现深拷贝
8、C++中的内存分区,
分别是堆、栈、自由存储区、全局/静态存储区、常量存储区和代码区。
栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限
堆:就是那些由 new
分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new
就要对应一个 delete
。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收
自由存储区:如果说堆是操作系统维护的一块内存,那么自由存储区就是C++中通过new和delete动态分配和释放对象的抽象概念。需要注意的是,自由存储区和堆比较像,但不等价。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量和静态变量又分为初始化的和未初始化的,在C++里面没有这个区分了,它们共同占用同一块内存区,在该区定义的变量若没有初始化,则会被自动初始化,例如int型变量自动初始为0
常量存储区:这是一块比较特殊的存储区,这里面存放的是常量,不允许修改
代码区:存放函数体的二进制代码
二.熟悉STL六大组件的使用,了解各容器的底层实现。
9、常见容器性质总结?
1.vector 底层数据结构为数组 ,支持快速随机访问
2.list 底层数据结构为双向链表,支持快速增删
3.deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问
deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:
[堆1] --> [堆2] -->[堆3] --> …
每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.
4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)
6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
使用重载排序时应注意重载cmp函数时>是从小排序,和其他相反
7.set 底层数据结构为红黑树,有序,不重复
8.multiset 底层数据结构为红黑树,有序,可重复
9.map 底层数据结构为红黑树,有序,不重复
10.multimap 底层数据结构为红黑树,有序,可重复
11.unordered_set 底层数据结构为hash表,无序,不重复
12.unordered_multiset 底层数据结构为hash表,无序,可重复
13.unordered_map 底层数据结构为hash表,无序,不重复
14.unordered_multimap 底层数据结构为hash表,无序,可重复
三.掌握面向对象的编程思想,理解类与类之间的关系,了解面向对象的设计原则。
面向对象设计原则
一个优良的系统设计,强调要保持低耦合、高内聚的关系
SOLID的5原则 ==》基本原则
- 单一职责原则 (Single Responsibility Principle)
一个类,最好只做一件事,只有一个引起它变化的原因
此原则的核心就是解耦和增强内聚性
2.开闭原则 (Open Closed Principle)
对扩展开放,对修改闭合
核心思想就是对抽象编程(继承和多态实现)
-
里氏替换原则 (Liscov Substitution Principle)
派生类可以扩展基类的功能,但不能改变基类原有的功能
-
接口分离原则 (Interface Segregation Principle)
使用多个小的专门的接口,而不要使用一个大的总接口。
-
依赖倒置原则 (Dependency Inversion Principle)
面向接口编程,依赖于抽象(抽象是稳定的,具体的是在变化的) -
迪米特法则 (Law of Demeter -> Least Knowledge Principle)
一个软件实体应当尽可能少地与其他实体发生相互作用
-
组合复用原则 (Composite/Aggregate Reuse Principle)
尽量采用组合、聚合的方式而不是继承的方式来达到软件复用的目标
四.掌握C++11新特性。(移动语义,智能指针)
移动语义:就是将临时对象转移到新对象中去
8、C++提供了哪几种智能指针,其各自的特点是什么?
使用时机:
实现多态时基类指向派生类对象时,可以使用智能指针。
auto_ptr 可以进行复制构造或者赋值操作,但是有bug
unique_ptr
1. 独享所有权的智能指针
2. 不具备值语义,不能进行拷贝构造或者赋值操作
3. 具有移动语义,可以发生转移
4. 可以作为容器的元素
shared_ptr
1. 共享所有权的智能指针
2. 具备值语义,可以进行复制或者赋值操作(主打特点) 引入了引用计数,执行复制或者赋值操作时,引用计数加1; 当一个shared_ptr被销毁时,引用计数减1,直到为0时,才真正释放托管的对象
3. 具有移动语义,可以发生转移
4. 可以作为容器的元素
5. 又称为强引用的智能指针
weak_ptr
1. weak_ptr 是一个弱引用的智能指针
2. 当weak_ptr执行拷贝构造或赋值操作时,不会导致引用计数加1
3. weak_ptr知道所托管的对象是否还存活 expired方法完成
4. weak_ptr不能直接访问对象,没有重载->和 *运算符
5. weak_ptr如要访问资源,必须提升成为shared_ptr才行 lock方法完成
智能指针使用场景:
实现多态创建基类指针指向派生类对象时,这里可以使用智能指针。
2.数据结构和算法:
掌握常用的数据结构,例如链表、栈、队列、二叉树、哈希表。了解红黑树、B树、B+树等。
掌握八大排序算法。
冒泡排序(bubble sort) — O(n2) 插入排序 (insertion sort)— O(n2) 归并排序 (merge sort)— O(n log n)
#十大排序中的非稳定排序
面试考察中一般问快排,选择,希尔,堆这几种非稳定排序
3.网络编程:
熟悉Linux下的Socket编程、熟悉TCP协议,TCP报文头,掌握网络IO复用模型select/epoll。
网络socket本地socket区别
本地:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。
IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的
网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径
五种I/O模型
1、OSI七层协议
2、工业标准 4层或5层协议
3、数据在网络中发送时的情况
4、传输层TCP协议
5、TCP建立连接时会采用三次握手,为什么?
答:1. 要确保双方确实已经收到了对方的确认,
连接建立成功
- 防止大量出现被延迟的SYN报文到达服务器时,
产生的问题
6、2次握手,可以不?
第一种情况,第一次握手
SYN报文被延迟
当客户端重发SYN之后,与服务器建立好了连接,并且完成了数据的通信。之后,被延迟的SYN报文此时到达了服务器,服务器会认为客户端重新要建立连接,因此会给客户端回复一个ACK报文,此时服务器就开始等待客户端发送数据。很显然,此时客户端根本就没有建立连接的需求会造成服务器资源的浪费
第二种情况,第二个报文ACK在网络中消失,没有被client接收到服务器因为已经发生了ACK报文,它认为连接已经建立好了,傻傻地等待客户端发数据过来,造成服务器资源的浪费;此时客户端根本就没有收到,因此认为该连接没有建立成功的
8、TCP连接断开时,要采用四次挥手,为什么?
答:TCP是一个全双工的协议。全双工的含义可以同时进行数据的读写操作,互不干扰。全双工当连接断开时,要做到两个方向上的数据传输全部断开。
1)第一次client发送FIN报文时,表示client已经发送完了所有的数据,再没有数据要发送给server了,但还可以
接收数据
2)当server收到FIN报文,并进行确认发送ACK时,表示它已经知道了client没有数据发送了。
3)当server也没有数据要发送给client时,发送FIN报文给client
4)当client收到FIN时,最后一次给server发送ACK报文;当ACK报文到达服务器时,就断开连接
9、服务器是否可以主动断开连接呢?
答: 可以。如果服务器主动断开连接, 会进入到TIME_WAIT状态,该状态持续2MSL的时间;
此时服务器不能做其他的事情,是对服务器资源的一种浪费,因此一般情况下,服务器不会主动断开连接。
但服务器是可以主动断开连接的,同时为了防止出现服务器资源浪费的情况,会设置套接字属性可以重用
10、 为什么需要TIME_WAIT状态?
答:1)防止出现连接无法正常断开的情况
2) 防止出现数据蹿链的情况
11、删除TIME_WAIT状态,是否可行?
一种情况:
假设server端没有收到第4次挥手ACK服务器认为连接没有断开,因此它会重发FIN报文,等待客户端给其回复ACK
客户端由于已经发送了ACK报文,它认为该连接已经断开了,因此不会再接收FIN报文了
总结:这样会导致该连接无法正常断开
第二种情况:数据蹿链
一个tcp连接由协议类型、源IP/源port、目的IP/目的port五元组信息构成
第一次起了一个连接,也完成了通信过程,连接关闭了;当再起一个新连接时,其五元组与第一次是相同的;
此时如果第一次连接进行过程中,在网络中被延迟的数据到达了第二个连接,是一定会被第二个连接接收的。因此就会造成数据蹿链的问题
在网络中被延迟的数据指的是确认重传的数据
12、Socket
13、epoll 与select的对比
14、水平触发 vs 边缘触发
15、发送数据时,若一次没有发送完成,需要执行滑动窗口机制
16、mmap
掌握并发服务器模型Reactor。
掌握Http协议。
17、HTTP全称超文本传输协议。
超文本:①HTTP是文本协议,这意味着HTTP报文的头部数据是文本类型的,用户可以直接阅读报文内容。
②最初载荷类型是超文本数据,现在可以载荷其他类型数据。
传输:①HTTP采用客户端-服务端的传输模型。
②HTTP是一种无状态的协议。
协议:HTTP是一种应用层协议,在TCP/IP协议族处于第四层,在ISO/OSI体系中处于第七层,其依赖的传输层是
可靠的协议(比如TCP协议、QUIC协议)。
18、URI由哪些部分组成?
scheme:方案名,默认HTTP,常见的还有HTTPS,FTP
://
userinfo:用户名,密码
@
host:port:域名,IP4/IP6;端口号,http的80、https的443可以省略
/路径:资源在服务器中的路径,默认/,后边可以省略
?查询字符串:键值对,用&连接
#分页符
19、HTTP请求报文和响应报文由哪些部分组成?
###请求报文
1、报文头:起始行:方法,URI,版本;首部字段;空行。
2、报文体。
###响应报文
1、报文头:起始行:版本,状态码,原因字符串;首部字段;空行。
2、报文体。
20、HTTP常用方法有哪些?
GET:“读”,用来获取资源
POST:“写”,用来提交表单给对应的资源,这个请求通常来说会修改资源的状态(称为有副作用的)。
PUT:用请求的内容替换掉目标资源的内容。
DELETE:删除目标资源
HEAD:用来获取资源的首部字段(不获取响应体)。
21、HTTP常用状态码有哪些?
200 ok 请求成功
301 Moved Permanently 永久重定向
302 Found 临时重定向
400 Bad Request 语法无效
403 Forbidden 无权限
404 Not Found 无资源
405 Method Not Allowed 不允许的方法
500 Internal Server Error 服务端错误
501 Not Implemented 服务器不支持请求方法
502 Bad Gateway 错误的响应
504 Gateway Timeout 响应超时
22、form-data的boundary起什么作用,有什么限制?
作用:作为请求报文报文体中key-value的分隔符
限制:长度不能超过70个字符,传输的内容不能与boundary相同,最后一个boundary后面会添加--作为整个请求体的结尾。
23、 简要说明一下HTTPS的握手阶段流程。
Client hello客户端向服务端发送连接请求,并发送支持的TLS版本,支持的加密算法
Server helo服务端向客户端发送连接请求,回复加密算法
Certificate服务端验证证书
Server key exchange服务端向客户端发送通过算法生成的公钥
Server finished服务端发送密钥完成
Client key exchange客户端向服务端发送算法生成的公钥
Client hello客户端向服务端发送连接确认
Finished完成连接
Finished完成连接
24、知道DNS是什么?
官方解释:DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。
通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。
通俗的讲,我们更习惯于记住一个网站的名字,比如www.baidu.com,而不是记住它的ip地址,比如:167.23.10.2
25、HTTPS和HTTP的区别
1、HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全, HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
2、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。 3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
26、在浏览器中输入url地址后显示主页的过程?
- 根据域名,进行DNS域名解析;
- 拿到解析的IP地址,建立TCP连接;
- 向IP地址,发送HTTP请求;
- 服务器处理请求;
- 返回响应结果;
- 关闭TCP连接;
- 浏览器解析HTML;
- 浏览器布局渲染;
4.操作系统:
一、熟悉使用linux(ubuntu18.04)。
什么是Linux?
Linux是一种自由和开放源代码的类Unix操作系统,它是由Linus Torvalds和其他开发人员共同创建的。
Linux和Unix有什么区别?
Linux是基于Unix的,但它是一个独立的操作系统。Unix是商业化的操作系统,而Linux是开源的。
什么是Shell?
Shell是Linux中的命令行解释器,它是用户与内核之间的接口。用户可以使用Shell来执行命令、编辑文件、管理文件系统等操作。
什么是文件系统?
文件系统是计算机中用来管理文件和目录的一种方式。Linux中的文件系统包括Ext2、Ext3、Ext4等。
什么是进程?
进程是正在运行的程序的实例。每个进程都有自己的内存空间、文件句柄等资源。
Linux中如何查看进程?
可以使用ps命令来查看当前运行的进程,也可以使用top命令来查看进程的资源使用情况。
Linux中如何安装软件包?
可以使用包管理器来安装软件包,如yum、apt-get等。也可以手动下载软件包并使用源代码编译安装。
Linux中如何查看系统日志?
可以使用日志文件来查看系统日志,如/var/log/messages、/var/log/syslog等。
Linux中如何设置文件权限?
可以使用chmod命令来设置文件权限,如chmod +x filename可以将文件设置为可执行权限。
Linux中如何设置环境变量?
可以使用export命令来设置环境变量,如export PATH=$PATH:/usr/local/bin可以将/usr/local/bin路径添加到PATH环境变量中。
Linux中如何查看网络配置?
可以使用ifconfig命令来查看网络配置,如ifconfig eth0可以查看eth0网卡的配置信息。
Linux中如何查看硬件信息?
可以使用lshw命令来查看硬件信息,如lshw -short可以查看系统中的所有硬件设备。
Linux中如何查看磁盘使用情况?
可以使用df命令来查看磁盘使用情况,如df -h可以查看磁盘的使用情况和可用空间。
Linux中如何查看系统负载?
可以使用uptime命令来查看系统负载情况,如uptime可以查看系统的负载平均值。
Linux中如何创建用户?
可以使用useradd命令来创建用户,如useradd username可以创建一个名为username的用户。
Linux中如何修改用户密码?
可以使用passwd命令来修改用户密码,如passwd username可以修改名为username的用户的密码。
Linux中如何安装服务?
可以使用系统自带的包管理器来安装服务,如yum install httpd可以安装Apache Web服务器。
Linux中如何启动和停止服务?
可以使用systemctl命令来启动和停止服务,如systemctl start httpd可以启动Apache Web服务器。
Linux中如何查看系统版本?
可以使用uname命令来查看系统版本,如uname -a可以查看详细的系统信息。
Linux中如何查看当前用户?
可以使用whoami命令来查看当前用户,如whoami可以查看当前登录用户的用户名。
Linux中如何设置定时任务?
可以使用crontab命令来设置定时任务,如crontab -e可以编辑当前用户的定时任务。
Linux中如何远程登录?
可以使用ssh命令来远程登录,如ssh username@host可以远程登录到名为host的主机,并使用username账号进行登录。
Linux中如何设置防火墙?
可以使用iptables命令来设置防火墙,如iptables -A INPUT -p tcp --dport 80 -j ACCEPT可以允许80端口的TCP流量通过防火墙。
Linux中如何查看当前系统使用的内存和CPU情况?
可以使用top命令来查看当前系统使用的内存和CPU情况,如top -n 1可以查看当前的系统状态,并在一秒钟后退出。
Linux中如何查看当前系统的IP地址?
可以使用ifconfig命令来查看当前系统的IP地址,如ifconfig eth0可以查看eth0网卡的IP地址。
Linux中如何设置系统时间?
可以使用date命令来设置系统时间,如date -s "2022-01-01 00:00:00"可以将系统时间设置为2022年1月1日0时0分0秒。
Linux中如何查看系统中的进程数量?
可以使用ps命令来查看系统中的进程数量,如ps -ef | wc -l可以查看当前系统中的进程数量。
Linux中如何查看系统中安装的软件包?
可以使用包管理器来查看系统中安装的软件包,如yum list installed可以查看当前系统中已安装的软件包。
Linux中如何查看系统中的网络连接?
可以使用netstat命令来查看系统中的网络连接,如netstat -an | grep ESTABLISHED可以查看当前系统中的已经建立的网络连接。
Linux中如何安装和配置Nginx Web服务器?
可以使用包管理器来安装Nginx Web服务器,如yum install nginx可以安装Nginx。配置文件位于/etc/nginx/nginx.conf。
二、熟悉进程间通信,多线程编程、文件读写(buffer I/O,unbuffer I/O ,mmap )。
open, read, write, lseek及close unbuffer I/O
fopen, fread, fwrite, fclose buffer I/O
例如 迅雷下载文件, ftruncate 截断文件,提前确定文件大小。
内存虚拟化
1.进程通信的方式:
管道、命名管道、信号、消息队列、共享内存、内存映射、信号量、Socket
-
管道 管道也叫无名(匿名)管道,它是是 UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制。管道本质其实是内核中维护的一块内存缓冲区,Linux 系统中通过 pipe() 函数创建管道,会生成两个文件描述符,分别对应管道的读端和写端。无名管道只能用于具有亲缘关系的进程间的通信。
-
命名管道 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO文件。有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。
-
信号 信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
-
消息队列 消息队列就是一个消息的链表,可以把消息看作一个记录,具有特定的格式以及特定的优先级,对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程则可以从消息队列中读走消息,消息队列是随内核持续的。
-
共享内存 共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会称为一个进程用户空间的一部分,因此这种 IPC 机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种 IPC 技术的速度更快。
-
内存映射 内存映射(Memory-mapped I/O)是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
-
信号量 信号量主要用来解决进程和线程间并发执行时的同步问题,进程同步是并发进程为了完成共同任务采用某个条件来协调它们的活动。对信号量的操作分为 P 操作和 V 操作,P 操作是将信号量的值减 1,V 操作是将信号量的值加 1。当信号量的值小于等于 0 之后,再进行 P 操作时,当前进程或线程会被阻塞,直到另一个进程或线程执行了 V 操作将信号量的值增加到大于 0 之时。
-
Socket 套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。Socket 一般用于网络中不同主机上的进程之间的通信。
2.进程调度算法:
-
先来先服务(FCFS)调度算法 先来先去服务调度算法是一种最简单的调度算法,也称为先进先出或严格排队方案。每次调度都是从后备作业(进程)队列中选择一个或多个最先进入该队列的作业(进程),将它们调入内存,为它们分配资源、创建进程,当每个进程就绪后,它加入就绪队列。当前正运行的进程停止执行,选择在就绪队列中存在时间最长的进程运行。
-
短作业优先(SJF)调度算法 短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业(进程),将它们调入内存运行,短进程优先(SPF)调度算法从就绪队列中选择一个估计运行时间最短的进程,将处理机分配给它,使之立即执行,直到完成或者发生某件事而阻塞时,才释放处理机。
-
优先级调度算法 优先级调度算法又称优先权调度算法,该算法既可以用于作业调度,也可以用于进程调度,该算法中的优先级用于描述作业运行的紧迫程度。在作业调度中,优先级调度算法每次从后备作业队列中选择优先级最髙的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列;在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理机分配给它,使之投入运行。
-
高响应比优先调度算法 高响应比优先调度算法主要用于作业调度,该算法是对 FCFS 调度算法和 SJF 调度算法的一种综合平衡,同时考虑每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行。
-
时间片轮转调度算法 时间片轮转调度算法主要适用于分时系统。每次调度时,把 CPU 分配给队首进程,并令其执行一个时间片。时间片的大小从几 ms 到几百 ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。
-
多级反馈队列调度算法 多级反馈队列调度算法是时间片轮转调度算法和优先级调度算法的综合和发展,通过动态调整进程优先级和时间片大小,多级反馈队列调度算法可以兼顾多方面的系统目标。
3.分段、分页、段页式
标准回答
- 分段
**将用户程序地址空间分成若干个大小不等的段,**每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,实现了离散分配。分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。 - 分页
用户程序的地址空间被划分成若干固定大小的区域,称为“页”,**相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。**分页主要用于实现虚拟内存,从而获得更大的地址空间。 - 段页式
页式存储管理能有效地提高内存利用率(解决内存碎片),而分段存储管理能反映程序的逻辑结构并有利于段的共享。将这两种存储管理方法结合起来,就形成了段页式存储管理方式。
段页式存储管理方式即先将用户程序分成若干个段,再把每个段分成若干个页,并为每一个段赋予一个段名。在段页式系统中,为了实现从逻辑地址到物理地址的转换,系统中需要同时配置段表和页表,利用段表和页表进行从用户地址空间到物理内存空间的映射。
系统为每一个进程建立一张段表,每个分段有一张页表。段表表项中至少包括段号、页表长度和页表始址,页表表项中至少包括页号和块号。在进行地址转换时,首先通过段表查到页表始址,然后通过页表找到页帧号,最终形成物理地址。
ps -ef | grep 进程号
top
4.什么是孤儿进程,什么是僵尸进程,他们对操作系统有什么影响?
孤儿进程:子进程还在运行,父进程已经终止,孤儿进程会被1号进程收养
僵尸进程:子进程已经终止,但父进程没有为子进程"收尸",此时的子进程是僵尸进程
当进程终止时,Linux内核里面会为终止的进程保留一些信息(pid,终止状态,CPU总时间)
父进程可以调用wait或者waitpid获取这些信息
当子进程终止时,内核会给父进程发送SIGCHLD信号,父进程默认处理:忽略
5.进程的结束方式有哪些?return和exit有什么区别?
1、return返回函数值,是关键字; exit 是一个函数。
2、return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
3、return是函数的退出(返回);exit是进程的退出。
4、return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。
5、return用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出, 非0 为非正常退出。
6、非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。
7、进程、进程组、会话和控制终端的关系是什么样的?
每个进程都属于一个进程组。
进程组:一个或多个进程的集合。(一个进程只能隶属于一个进程组)
会话:一个或多个进程组的集合
控制终端
(1)一个会话可以有0个或者1个控制终端
(2)如果会话有控制终端,会话的首进程也被称为控制进程
(3)与终端相连的会话,有一个前台控制组,有0个或多个后台控制组
(4)前台进程组的成员可以响应键盘中断信号,可以读取终端输入,后台进程组则不会
(5)会话与终端断开连接,会话首进程会收到SIGHUP信号
8、守护进程和后台进程组的区别是什么?
(1)守护进程已经完全脱离终端控制台了,而后台程序并未完全脱离终端(在终端未关闭前还是会往终端输出结果);
(2)守护进程在关闭终端控制台时不会受影响,而后台程序会随用户退出而停止,需要在以nohup command &格式运行才能避免影响;
(3)守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没改变;
9、无名管道和命名管道有哪些特点和区别?
命名管道:有名管道是文件系统中一种专门用来读写的文件,但是通过有名管道进
行通信的时候实际上并没有经过磁盘,而是经过内核的管道缓冲区进行数据传递。
特点:FIFO可以在无关的进程之间交换数据,与无名管道不同;FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
无名管道:它不需要在文件系统创建单独的文件,相反它是进程在执行过程中动态创建和销毁的。
特点:它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端;它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间);它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
10、什么是线程?线程和进程有哪些区别?
线程:一条执行流程
进程:CPU分配资源的最小单位
线程:CPU调度是最小单位
11、线程的标识
pthread_self
获取线程的id
pthread_t pthread_self(void);
pthread_t
表示线程的id
pthread_equal
比较两个线程是否相等
int pthread_equal(pthread_t t1, pthread_t t2);//相等返回非0值
pthread_create
创建一个新的线程
线程的创建
pthread_create
创建一个新的线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
thread
:线程的id(传入传出参数,因为用户不能指定线程的id,必须由内核分配)
attr
(attribute):线程的属性,一般直接填NULL,表示默认属性
start_routine
(本质:函数指针,返回值(void *) ( * ) (void *)):线程的入口函数,类似于进程的main函数
void *
:是通用指针,可以转化为其他任意类型的指针
void *
:在32位系统上占4个字节,在64位系统上占8个字节,因此void *可以和整型数值与浮点数值进行转换
arg
:线程入口函数的参数
返回值
:成功返回0,失败非0
pthread_create
是新线程先执行还是旧线程先执行是不确定的(类比fork是竞态条件的滋生地)
线程的终止:
(1)线程的入口函数返回,返回值为线程的退出码
(2)线程可以被同进程中的其他线程取消pthread_cancel
int pthread_cancel(pthread_t thread);
(3)线程自己调用pthread_exit
(只会导致线程退出)(其中参数retval
为线程的退出码)
void pthread_exit(void *retval);
互斥锁mutex
(保护共享资源,避免出现数据不一致的现象)
mutual exclusive
类型:pthread_mutex_t
(互斥锁)
一、pthread_mutex_init
与pthread_mutex_destory
销毁与初始化
pthread_mutex_init
初始化锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);//动态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化
pthread_mutex_destory
销毁锁,得到未初始化的锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
(1)如果锁是静态变量,可以采用静态方式进行初始化
(2)如果锁变量位于堆空间,是动态申请的,采用动态的方式初始化
(d)
pthread_join
线程会一直阻塞,等待目标线程终止(thread),目标线程终止时,会将退出码放入retval所指的位置
int pthread_join(pthread_t thread, void **retval);//线程退出的值会保留在retval所指的位置
12、线程的清理函数什么时候会执行?什么时候不会执行?
pthread_cleanup_push 负责将清理函数压入一个栈中,这个栈会在下列情况下弹出:
线程因为取消而终止时,所有清理函数按照后进先出的顺序从栈中弹出并执行。
线程调用 pthread_exit 而主动终止时,所有清理函数按照后进先出的顺序从栈中弹出并执行。
线程调用 pthread_clean_pop 并且 execute 参数非0时,弹出栈顶的清理函数并执行。
线程调用 pthread_clean_pop 并且 execute 参数为0时,弹出栈顶的清理函数不执行。
13、 为什么pthread_cleanup_push
和pthread_cleanup_pop
需要成对出现?
POSIX要求 pthread_cleanup_push 和 pthread_cleanup_pop 必须成对出现在同一个作用域当中,主
要是为了约束程序员在清理函数当中行为。下面是在 /urs/include/pthread.h 文件当中,线程清理函
数的定义:
可以发现push和pop的宏定义不是语义完全的,它们必须在同一作用域中成对出现才能使花括号成功匹
配。
14、使用条件变量的使用为什么一定要加锁?
阻止其他线程改变条件变量
生产者与消费者问题,
其实也就是多个线程的问题,但是会涉及到线程之间对共享资源的互斥访问,所以需要有互斥锁与条件变量的准备知识,接下来我们先看看互斥锁与条件变量。
互斥锁
在多线程编程中,用来控制共享资源的最简单有效也是最广泛使用的机制就是 mutex(MUTual
EXclusion) ,即互斥锁
条件变量
理论上来说,利用互斥锁可以解决所有的同步问题,但是生产实践之中往往会出现这样的问题:一个线
程能够执行取决于一个共享资源的具体数值,而该共享资源的数值会随着程序的运行不断地变化,线程
也经常在可运行和不可运行之间动态切换,假如纯粹使用互斥锁来解决问题的话,就会出现大量的重复
的“加锁-检查条件不满足-解锁”的行为,这样的话,不满足条件的线程会经常试图占用CPU资源,上下文
切换也会非常频繁。
对于这样依赖于共享资源这种条件来控制线程之间的同步的问题,我们希望采用一种无竞争的方式让多
个线程在共享资源处会和——这就是条件变量。条件变量一定要配合锁来使用,当A线程持有锁的时
候,A可以检查条件是否成立,假如条件不成立,A可以让自己阻塞并且解锁(条件变量保证了陷入阻塞
和解锁是原子的),此时锁被释放,其他的线程比如B线程可以持有锁(有些情况下可以不持有锁)去
修改条件的内容,一旦B认为现在是一个合适的时机唤醒A时,B可以通知到A线程。A线程收到通知之
后,会首先恢复运行并加锁,再继续执行后续的指令。使用条件变量可以明确A、B线程某些行为之间的
先后顺序,以实现同步的效果。
线程池
同一进程下的线程可以共享
线程共享的内容包括:
- 进程代码段
- 进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、
- 进程打开的文件描述符、
- 信号的处理器、
- 进程的当前目录和
- 进程用户ID与进程组ID
线程独有的内容包括:
- 线程ID
- 寄存器组的值
- 线程的堆栈
- 错误返回码
- 线程的信号屏蔽码
三、熟悉进程池、线程池的实现。
1、进程池:
四、简单了解分布式系统,消息队列。
线程池的实现
为什么要有线程池?假设没有使用线程池时,一个请求用一个子线程来处理。每来一个请求,都得创建
子线程,子线程执行请求,关闭子线程。当请求量(并发)比较大的时候,频繁地创建和关闭子线程,
也是有开销的。因此提出线程池,提前开辟好N个子线程,当有任务过来的时候,先放到任务队列中,
之后N个子线程从任务队列中获取任务,并执行,这样能大大提高程序的执行效率。其实当任务数大于
线程池中子线程的数目的时候,就需要将任务放到缓冲区(队列)里面,所以本质上还是一个生产者消
费者模型。
oo_threadpool
bo_threadpool
交互图:
github源代码链接:1750252467/ThreadPool (github.com)
5.设计模式:
-
熟悉单例模式。
-
源码及详细介绍博客:单例模式(C++)_泷fyk的博客-CSDN博客
方式一: 嵌套类 + 静态对象
方式二: 自动调用destroy方法,就可以回收单例对象
方式三: pthead_once + atexit,就可以回收单例对象(有环境限制,只能在linux环境下,实现了多线程安全)
-
了解常用的设计模式,如工厂模式、观察者模式。
类与类之间的关系—继承(泛化)
基类部分成为派生类的一部分
在语义上:A is B
在代码上:继承
继承与泛化的区别,继承是基类向派生类方向,泛化是从派生类向基类方向类与类之间的关系—关联
关联关系有两种形式:
双向的关联关系
单向的关联关系彼此并不负责对方的生命周期
在语义上:A has B,关系是固定的
在代码上:一般使用指针或者引用
类与类之间的关系—聚合
比较强的一种关联关系
对象之间的关系表现为整体和局部
整体部分并不负责局部对象的销毁
在语义上:A has B
在代码上:数据成员以指针或者引用形式存在
类与类之间的关系—组合
更强的一种关联关系
对象之间的关系表现为整体和局部
整体部分负责局部对象的销毁
在语义上:A has B
在代码上:数据成员以子对象成员形式存在
类与类之间的关系—依赖
从语义上来说是 A use B,是偶然的,临时的,并非固定的
在代码上:
B作为A的成员函数参数
B作为A的成员函数的局部变量(B作为A的成员函数返回值)
A的成员函数调用B的静态方法依赖 < 关联 < 聚合 < 组合 < 继承
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结
工厂模式
- 作用
用于创建复制对象,明确地计划不同条件下创建不同的实例。 - 优点
使代码结构清晰,能够更加有效的进行封装。对调用者屏蔽具体的产品实现。降低耦合度。 - 缺点
对于简单对象,使用工厂模式会增加其系统的复杂度。
代理模式(Proxy Pattern)【结构型模式】
- 作用
为其他对象提供一种代理来控制对某个对象的访问,在一些已有的方法在使用的时候需要对已有的方法进行拓展,可用此模式来完成。 - 优点
职责清晰,有更高的拓展性,更加的智能。 - 缺点
实现代理模式增加了工作量,且通过代理模式访问会使性能降低。
观察者模式(Observer Pattern)【行为型模式】
-
作用
用于在易用和低耦合下实现一个对象改变给其他对象通知的功能。 -
优点
观察者和被观察抽象耦合的。
- 缺点
当观察者较多时,完成通知耗时较长,观察者和被观察是循环依赖时,会导致循环调用,可能会使导致系统崩溃
- 缺点
- 作用
6.数据库:
掌握Linux下使用MySQL。
1、事务的四大基本特征是什么?
原子性:事务要么全部不发生,要么全部都发生
一致性:事务的执行的前后,数据的完整性保持一致。且事务执行前后数据的总和不变。
隔离性:一个事物内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事物之间不能
互相干扰。事务之间是串行执行的。
隔离级别有关系,隔离性不能达到100%
持久性:指一个事物一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障
不应该对其有任何影响
2、并发产生的四大基本问题是什么?
1、脏写
脏写是指当多个事务并发写同一数据时,先执行的事务所写的数据会被后写的数据覆盖
2、脏读
如果一个事务A向数据库写数据,但该事务还没提交或终止,另一个事务B就看到了事务A写入数据库的
数据,这个现象我们称为脏读。
3、不可重复读
一个事务有对同一个数据项的多次读取,但是在某前后两次读取之间,另一个事务更新该数据项,并且
提交了。在后一次读取时,感知到了提交的更新
4、幻读
一个事务前后两次读数据的时候,凭空多产生了一条,此时就像产生幻觉一样,称为幻读。T2事务前后
两次进行读操作的过程中,T1事务新增了一条数据,并且提交了,所以对于T2事务而言,产生幻读。
4、B树和B+树的区别?
B树:结点中既存key值,也存value,还要存指向孩子结点的指针,所以一个B树结点存储key值的数量
是有限的。
B+树:非叶子结点中存key值,不存value,还要存指向孩子结点的指针,所以一个B+树结点存储key值
的数量相对B树而言,数据量要大,这样树的高度就会降低,树的高度就是进行磁盘IO次数。
5、什么是最左前缀原则?
在组合索引中,如果要进行查询的时候,一定要出现组合索引中最左边的一列,在最左边一列相同情况
下,第二列才可能排好序,然后在第一列,第二列排好序的情况下,第三列才能局部有序,依次类推。
所以在进行查询的时候,如果不出现第一列,后面的列是不能保证有序的,就用不到索引。所以在查询
的时候一定要出现第一列
6、MyISAM与InnoDB各自有什么特点?
MyISAM存储引擎
特点:
a. 查询速度很快
b. 支持表锁
c. 支持全文索引(正排索引、倒排索引)
d. 不支持事务
InnoDB存储引擎
特点:
a. 支持事务
b. 支持行锁和表锁(默认支持行锁)
c. 支持MVCC(多版本并发控制),版本号,事务的编号
d. 支持崩溃恢复
e. 支持外键一致性约束
5、MyISAM索引与InnoDB索引的区别?
MyISAM存储引擎
MySQL 5.5 之前默认的存储引擎。
特点:
a. 查询速度很快
b. 支持表锁
c. 支持全文索引(正排索引、倒排索引)
d. 不支持事务
使用 MyISAM 存储表,会生成三个文件.
.frm # 存储表结构,是任何存储引擎都有的
.myd # 存放数据
.myi # 存放索引
InnoDB存储引擎
MySQL 5.5 以及以后版本默认的存储引擎。没有特殊应用,推荐使用InnoDB引擎。
特点:
a. 支持事务
b. 支持行锁和表锁(默认支持行锁)
c. 支持MVCC(多版本并发控制),版本号,事务的编号
d. 支持崩溃恢复
e. 支持外键一致性约束
粒度
使用 InnoDB 存储表,会生成两个文件.
.frm # 存储表结构,是任何存储引擎都有的
.ibd # 存放数据和索引
6、临时表与存储引擎是memory的表有什么区别?
临时表创建完成之后,默认的存储引擎还是innodb,但是show tables看不到表的名字;如果将存在临时表的会话关闭,那么临时表就会消失,不会在存在了。
存储引擎是memory的表创建之后,存储引擎明显就不是innodb,而是memory,show tables的时候 是可以看到表的名字的,即使将会话关闭,存储引擎是memory的表,表是不会消失的。断电重启之后,表的结构还在,但是表中的内容消失了。
掌握Navicat操作MySQL。
掌握Redis。
1、 什么是Redis?有哪些有优缺点?
Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。
与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上
2、为什么要用 Redis /为什么要用缓存
主要从“高性能”和“高并发”这两点来看待这个问题。
高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
3、 Redis为什么这么快
1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
2)数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4)使用多路 I/O 复用模型,非阻塞 IO;
4、Redis有哪些数据类型
Redis相比其他缓存,有一个非常大的优势,就是支持多种数据类型。Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,虽然Redis不像关系数据库那么复杂的数据结构,但是,也能适合很多场景,比一般的缓存数据结构要多。
5、什么是Redis持久化?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
6、Redis 的持久化机制是什么?各自的优缺点?
Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制:
RDB:是Redis DataBase缩写快照
RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
优点:
- 1、只有一个文件 dump.rdb,方便持久化。
- 2、容灾性好,一个文件可以保存到安全的磁盘。
- 3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
- 4.相对于数据集大时,比 AOF 的启动效率更高。
缺点:
- 1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
- 2、AOF(Append-only file)持久化方式: 是指所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。
AOF:持久化
AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
优点:
- 1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 aof 文件中一次。
- 2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
- 3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
缺点:
- 1、AOF 文件比 RDB 文件大,且恢复速度慢。
- 2、数据集大的时候,比 rdb 启动效率低。
两者对比:
- AOF文件比RDB更新频率高,优先使用AOF还原数据。
- AOF比RDB更安全也更大
- RDB性能比AOF好
- 如果两个都配了优先加载AOF
7、 Redis key的过期时间和永久有效分别怎么设置?
EXPIRE和PERSIST命令。
我们知道通过expire来设置key 的过期时间,那么对过期的数据怎么处理呢?
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
- 定时去清理过期的缓存;
- 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
8、Redis事务的概念
Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
9、Redis事务的三个阶段
- 事务开始 MULTI
- 命令入队
- 事务执行 EXEC
10、Redis事务相关命令和事务的特征?
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
- redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
- 如果在一个事务中的命令(编译)出现错误,那么所有的命令都不会执行;
- 如果在一个事务中出现运行错误,那么正确的命令会被执行。
- WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
- MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
- EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
- 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
- UNWATCH命令可以取消watch对所有key的监控。
11、缓存穿透是什么?如何解决?
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
12、缓存击穿是什么?有什么解决方案?
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
- 设置热点数据永远不过期。
- 利用互斥锁保证同一时刻只有一个客户端可以查询底层数据库的这个数据,一旦查到数据就缓存至Redis内,避免其他大量请求同时穿过Redis访问底层数据库。
13、缓存雪崩的概念?如何应对?
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
-
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
-
一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
7.常用工具:
-
熟悉Windows下的Visual Studio/VS Code。
- F5 开始使用调试器运行程序
- F9 在当前行设置断点
- F10 运行到下一个断点处
- F5 从被调试的已停止程序恢复执行
- F11 步进到函数内(如果当前程序指针指向一个函数)
- F10 步过函数(如果当前程序指针指向一个函数)
- Shift+F11 步出执行的函数
-
掌握源码阅读工具SlickEdit;熟悉Linux下Vim编辑器,GNU GCC工具链(gcc/g++/gdb/cgdb)。
-
掌握makefile文件的编写,Git等版本控制系统。
makefile:
目标:依赖
命令
内置函数
git:
进入到工作目录 git status 查看状态。
git pull
写文件
git add 文件名
git commit -m “代码提交信息” 到HEAD中
git push
创建一个叫做"feature_x"的分支,并切换过去:
git checkout -b feature_x
切换回主分支:
git checkout master
再把新建的分支删掉:
git branch -d feature_x
除非你将分支推送到远端仓库,不然该分支就是 不为他人所见的:
git push origin <branch>
-
git commit --amend 好用,减少提交无用信息,可常用。
-
git reset --soft HEAD 回退,差异存在暂存区,也好用,不轻易删除代码。
-
git reset --hard HEAD 回退删除, 慎用,一不小心删除代码还不可逆。
-
git reset HEAD || git reset --mixed HEAD 回退,改变的差异在工作区。
cgdb
调试命令 (缩写) | 作用 |
---|---|
(gdb) break (b) | 在源代码指定的某一行设置断点,其中xxx用于指定具体打断点位置 |
(gdb) run (r) | 执行被调试的程序,其会自动在第一个断点处暂停执行。 |
(gdb) continue (c) | 当程序在某一断点处停止后,用该指令可以继续执行,直至遇到断点或者程序结束。 |
(gdb) next (n) | 令程序一行代码一行代码的执行。 |
(gdb) step(s) | 如果有调用函数,进入调用的函数内部;否则,和 next 命令的功能一样。 |
(gdb) until (u) (gdb) until (u) location | 当你厌倦了在一个循环体内单步跟踪时,单纯使用 until 命令,可以运行程序直到退出循环体。 until n 命令中,n 为某一行代码的行号,该命令会使程序运行至第 n 行代码处停止。 |
(gdb) print (p) | 打印指定变量的值,其中 xxx 指的就是某一变量名。 |
(gdb) list (l) | 显示源程序代码的内容,包括各行代码所在的行号。 |
(gdb) finish(fi) | 结束当前正在执行的函数,并在跳出函数后暂停程序的执行。 |
(gdb) return(return) | 结束当前调用函数并返回指定值,到上一层函数调用处停止程序执行。 |
(gdb) jump(j) | 使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。 |
(gdb) quit (q) | 终止调试。 |
- 使用过阿里云的OSS对象存储备份文件,并使用阿里云作为markdown的图床发布博客文章。
面试雷区
一、“我没有其他问题要问了…”❌
每当求职者面试即将结束时,面试官一般都会问道:你还有什么问题要问我们的吗?
漂亮的回答会给予面试官一种你认真准备面试的感觉,另外还可以考察你对本工作岗位的意向程度,如果屏幕前的你是有心人的话,是肯定的不会没问题的~~~
建议话术:✔
(提前围绕该岗位的未来发展、主要职责或工作内容、公司氛围、具体项目等准备问题…)
仅供参考****👇👇👇
1、请问如果我能顺利入职的话,主要负责的业务是哪块呢?主要的工作内容是怎样的呢?
2、请问贵部门的员工配置是怎样的呢?有多少员工呢?
3、我的个人职业规划大概是这样的…,请问您觉得当下我该如何做可以更好的适应入职的节奏或工作内容呢?
二、“期望薪资方面我没有什么想法…”❌
一个对薪资没有要求与想法的员工是很可怕的,上班就是为了赚钱,为了提高生活水平,你连薪资都没有要求,难道让领导靠感情来管理维系你吗?
这种回答会给HR一种不上进,没追求的感觉,或者就是心浮气躁,并没有对本次面试上心,只是一个匆匆体验客…
建议话术:✔
(提前调查该城市该行业该岗位的平均薪资水平,进而结合个人实习经历或自身能力进一步谈。中心思想:自身价值+个人追求)
仅供参考👇👇👇
您好,我在上家公司实习/工作的时候到手的薪资是xxx,结合我的工作经验和个人诉求,我的期望薪资是xxx,根据目前的行业前景和市场行情,我觉得我的要求是合适的,以上就是我个人的薪资期望,您这边觉得怎样呢?
三、“我对该岗位没有什么经验,但我会认真去学习哒…”❌
不要带着所谓的“学生思维”去表达自己的想法,公司不是学校,公司招人是需要你来创造价值的,你需要尽可能表现出自己的外在价值或潜在价值。
建议话术:✔
作为一名应届毕业生,虽然我没有相关的经验,但是之前有接触过这方面的内容(此处可以举个栗子),对xxx也有过比较深入的了解和熟悉,本岗位的主要工作内容我也在本领域的师兄/师姐/朋友那了解过,还是比较熟悉的。
另外,公司在xxx方面的发展和成绩我也很感兴趣,十分期待自己能够顺利加入这个团队,贡献自己的一份力量!💯
github
1750252467/ThreadPool (github.com)