1 项目或实习经历中有解决比较有挑战的工程实际问题吗?具体的问题?
2 为什么协程比实际线程更轻量?
1 项目或实习经历中有解决比较有挑战的工程实际问题吗?具体的问题?
海康威视网络摄像头,视频流分帧转图像,加ros时间戳
2 为什么协程比实际线程更轻量?
(1)协程切换完全在用户空间进行,线程切换涉及特权模式切换,需要在内核空间完成;
不依赖操作系统和其提供的线程,golang自己实现的CSP并发模型实现:M, P, G
go协程也叫用户态线程,协程之间的切换发生在用户态。在用户态没有时钟中断,系统调用等机制,因此效率高
(2)协程切换相比线程切换做的事情更少。
执行go协程只需要极少的栈内存(大概是4~5KB),默认情况下,线程栈的大小为1MB。
goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。
所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行。
3 goroutine怎么做到的不涉及内核态的切换?
协程切换只是简单地改变执行函数栈,不涉及内核态与用户态转化,也不涉及上下文切换,开销远小于进程/线程切换。
4 怎么保证数据库数据不发生冲突
事务+锁
5 共享数据如何保证线程安全
主要看数据的隔离级别,允不允许出现幻读脏读不可重复读。假设场景是订票,共享数据是票,B线程执行订票未提交时,A线程执行查票。A可能会把已订的票查出来,但是提交订票时就会订不到该票。这种情况虽然查票数据不对,但是也是允许出现的。如果A线程也执行的是订票程序,那就必须进行同步(方法、集合、对象上锁)。线程安全和效率是矛盾的,需要进行折中,一般不建议在方法上同步,太慢了
加锁,各种锁,共享锁,互斥锁,等等
6 redis的数据结构
https://www.runoob.com/redis/redis-data-types.html
7 redis哈希结构可以正常设置超时吗?
Redis中有个设置时间过期的功能,即通过setex或者expire实现,目前redis没有提供hsetex()这样的方法,redis中过期时间只针对顶级key类型,对于hash类型是不支持的
8 redis设置过期时间
EXPIRE 接口定义:EXPIRE key “seconds”
接口描述:设置一个key在当前时间"seconds"(秒)之后过期。返回1代表设置成功,返回0代表key不存在或者无法设置过期时间。
127.0.0.1:6379> set aa bb
OK
127.0.0.1:6379> EXPIRE aa 60
(integer) 1
127.0.0.1:6379> EXPIRE aa 600
(integer) 1
9 用户级线程和内核级线程
线程的实现可以分为两类:用户级线程(User-Level Thread)和内核线线程(Kernel-Level Thread),后者又称为内核支持的线程或轻量级进程。在多线程操作系统中,各个系统的实现方式并不相同,在有的系统中实现了用户级线程,有的系统中实现了内核级线程。
用户线程:指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,用户进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快,操作系统内核不知道多线程的存在,因此一个线程阻塞将使得整个进程(包括它的所有线程)阻塞。由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少。
**内核线程:**由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。Windows NT和2000/XP支持内核线程。
以下是用户级线程和内核级线程的区别:
(1)内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
(2)用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
(3)用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
(4)在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
(5)用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。
https://blog.csdn.net/debugingstudy/article/details/12668389
10 线程之间同步的方式
互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
1、 互斥量/互斥锁(CMutex)
互斥量多用于多进程之间的线程互斥,用来确保一个线程独占一个资源的访问。
2、 信号量(CSemphore)
PV操作
当需要一个计数器来限制可以使用某共享资源的线程数目时,可以使用“信号量”对象。
信号量提供对临界资源的安全分配。如果存在多份临界资源,在多个线程争抢临界资源的情况下,向线程提供安全分配临界资源的方法。如果临界资源的数量为1,将退化为锁。
IPC方式中也有信号量,常常配合ipc共享内存来使用,作为进程之间以及同一进程不同线程间的同步手段。
3 条件变量
条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
https://blog.csdn.net/drdairen/article/details/73480570
11 C++ 匿名函数
C++匿名函数,其实就是lambda函数(lambda表达式),其实就是没有名字的函数。使用匿名函数,可以免去函数的声明和定义。这样匿名函数仅在调用函数的时候才会创建函数对象,而调用结束后立即释放,所以匿名函数比非匿名函数更节省空间
对一些STL容器函数sort,find等,其最后的一个参数时函数指针,我们也可以使用匿名函数来完成
#include<vector>
#include<algorithm>
#include<iostream>
void main()
{
std::vector<int> a(5);
a[0] = 3;
a[1] = 4;
a[2] = 5;
a[3] = 6;
a[4] = 7;
std::for_each(std::begin(a), std::end(a), [](int Feature){std::cout << Feature << std::endl; });
}
12 堆(优先队列)数据结构
堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
堆分为两种:大顶堆和小顶堆,两者的差别在于节点的排序方式。
在大顶堆中,父节点的值比每一个子节点的值都要大。在小顶堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
C++中借助优先队列实现大顶堆和小顶堆
大顶堆:
//构造一个空的优先队列(此优先队列默认为大顶堆)
priority_queue<int> big_heap;
//另一种构建大顶堆的方法
priority_queue<int,vector<int>,less<int> > big_heap2;
小顶堆:
//构造一个空的优先队列,此优先队列是一个小顶堆
priority_queue<int,vector<int>,greater<int> > small_heap;
堆和普通树的区别
堆并不能取代二叉搜索树,它们之间有相似之处也有一些不同。我们来看一下两者的主要差别:
节点的顺序:在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。
**内存占用:**普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配内存。堆仅仅使用一个数据来存储数组,且不使用指针。
**平衡:**二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(log n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足堆属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(log n) 的性能。
**搜索:**在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。
堆在数组中是如何存储的:
如果 i 是节点的索引,那么下面的公式就给出了它的父节点和子节点在数组中的位置:
parent(i) = floor((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2
https://www.jianshu.com/p/6b526aa481b1
13 堆和栈内存分配区别
**栈:**只要栈剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
**堆:**首先应该知道操作系统有一个记录空闲内存地址链表,当系统收到程序申请时,会遍历该链表,寻找第一个空间大于所申请空间堆结点,然后将该结点从空闲结点链表中删除,并将该结点空间分配给程序,另外,对于大多数系统,会在这块内存空间中首地址处记录本次分配大小,这样,代码中delete语句才能正确释放本内存空间。另外,由于找到堆结点大小不一定正好等于申请大小,系统会自动将多余那部分重新放入空闲链表中。
栈,英文是 stack,在内存管理的语境下,指的是函数调用过程中产生的本地变量和调用数据的区域。这个栈和数据结构里的栈高度相似,都满足“后进先出”(last-in-first-out 或 LIFO)。
栈上的内存分配极为简单,移动一下栈指针而已。
栈上的释放也极为简单,函数执行结束时移动一下栈指针即可。
由于后进先出的执行过程,不可能出现内存碎片。
14 SQL语句执行顺序
先from表,order by 一般是靠近后面执行的,limit最后执行
15 哈希表底层结构
数组+链表实现
16 GET和POST分别能产生几个请求包
GET和POST还有一个重大区别,简单的说:
GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。
17 GET与POST区别
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark,而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中。
18 什么情况下用到事务,事务可以解决什么问题
当数据库需要处理操作量大、复杂度高的数据的时候需要用到事务。
比如说,在学生管理系统中,你删除一个学生,你即需要删除学生的基本资料,也要删除和该学生相关的信息,如已选课程、成绩等等,这是对数据库操作就要使用事务,要删除学生信息就把所有相关信息都删除,要不就都不删除!
用事务是为了保证数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
19 只有两次握手会有什么问题
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。
https://blog.csdn.net/ljq140421/article/details/77864208
20 增删查改综合性能都很好的数据结构?
哈希表?
查找,插入和删除都是O(1)
21 进程的优势
进程优点: 每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
缺点: 需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大。
线程优点 : 无需跨进程边界;
缺点 : 每个线程与主程序共用地址空间,受限于2GB地址空间;
多线程的优点:线程间切换快,共享数据,多核cpu利用率
多线程的缺点:多线程同步、加锁控制较负责;
多进程的优点:独立
多进程的缺点:调度开销大
https://www.cnblogs.com/stay-sober/p/4173712.html
22 什么是野指针,如何避免野指针
野指针: 随机指向内存中的一个地址,对于这个地址不一定有访问权
如何避免野指针:
(1)当指针没有做初始化,即没有指向时,将指针指为NULL。一方面可以提醒自己这个指向NULL的指针不可操作不可访问,另一方面NULL这个标记便于我们检查和避免野指针;
初始化为NULL的目的:一是出现段错误时易改错,二是(void *0) 是0地址,是不允许操作,不允许访问的。
(2)当想给指针赋值时,检查是否已经给他分配了内存空间,如果没有分配就再用malloc分配;
(3)给指针分配完内存后,不使用时再用free()函数清空内存空间(清空原来的缓冲区),并再将指针指为NULL。
注: void * 为万能指针,可以接收任何类型的指针,但是不能对其取值(不能对他指向的空间进行操作,他只能暂时存放地址)
23 C++虚函数怎么实现多态
C++主要通过虚函数表和虚表指针实现多态。
多态是c++的特点之一,关于多态,简而言之就是 用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种方法呢,可以让父类的指针具有多种形态,也就是说不需要改动很多的代码就可以让父类这一种指针,干一些很多子类指针的事情,这里是从虚函数的实现机制层面进行研究。
虚表指针:
class A{
public:
void func1(){}
void func2(){}
virtual void vfunc(){}
};
int main(){
A a;
cout << sizeof(a) << endl; //4
}
空类大小为1,成员函数不占用类空间,含有虚函数的类大小为4,因为插入了虚表指针。
当类中含有虚函数时,编译器会自动在类中添加虚表指针:
class A{
public:
void *vptr; //4字节,虚函数表指针(virtual table pointer)
//......
};
int main(){
A a;
cout << sizeof(a) << endl; //4
}
虚函数表
当类中含有虚函数时,编译器就会自动为该类生成虚函数表。
虚函数表的赋值:
编译过程中,在含有虚函数的类的构造函数中,为自动为虚表指针赋值,指向相应的类的虚函数表。
A(){
vptr = &A::vftable; //使vptr指向类A的vtbl
}
类A的对象在内存中的布局
使用类A实例化一个对象a,对象a的大小是12字节,分别是虚函数表指针的大小,两个成员变量的大小。
其中,对象a的虚函数表指针指向类A的虚函数表,虚函数表中保存着类A的三个虚函数的地址。
虚函数使用
有继承关系时,父类与子类的内存布局
父类有三个虚函数f(), g(), h(),
其中,派生类重写了g()
,则派生类的虚函数表中会更新函数g()
的地址,没有被重写的虚函数还是指向原来父类的虚函数的地址
24 一个空的类,编译器默认生成哪些成员函数
缺省构造函数。
缺省拷贝构造函数。
缺省析构函数。
缺省赋值运算符。
C++中创建一个空类:
class Empty {};
默认会生成4个函数,其函数的原型如下:
public: Empty() { … }
Empty(const Empty& rhs) { … }
~Empty() { … }
Empty& operator=(const Empty& rhs) { … }
说明:
(1) 这些函数只有在需要调用的时候,编译器才会生成。
(2) 4个函数都是public的。
(3) 4个函数都是inline的(即函数定义在类的定义中的函数)。
(4) 如果你显式的声明了这些函数中的任何一个函数,那么编译器将不再生成默认的函数。
25 malloc申请的内存会自动初始化吗
不会,需要自己进行初始化。
malloc分配内存后,这个块是未经初始化的。一般情况下,我在程序中,都是直接置0的。
memset(p, 0x0, length);
26 STL迭代器失效
https://blog.csdn.net/baidu_37964071/article/details/81409272
27 统计一个文档(考虑大文件)中出现频率最多的k个单词
这个问题就是最常见的topK问题,解决思路:首先统计文档中所有不同word出现的频率,然后对所有不同的word按照出现频率排序,取出出现频率最大的k个words。
1.统计文档中所有不同word出现的频率
统计文档中word的频率的方法,要根据文档的数据量来决定:
(1)如果文档中数据能够全部读入内存,那么可以通过map/hashmap来直接统计各个word出现的频率。之所以采用map/hashmap结构,是因为它们的查找,修改效率很高,map在对数级别,hashmap在常数级别。
(2)如果文档中的word的数据不足以全部读入内存甚至远远超过了内存的容量,那只能通过分治的思想来解决,其中对于大数据比较好的解决办法:将这个文档通过hash(word)%n,hash到n个不同的小文件中,n根据文档的大小以及内存空间而定,hash后,所有相同的word肯定会在同一个文件中,然后分别对这n个文件分别利用map/hashmap来统计其中word的频率,分别求出topk,最后进行合并求总的topk。
当所有不同word的频率求出来之后,就是如何求出topk的问题了
可以建一个小顶堆
https://blog.csdn.net/iteye_18480/article/details/82512116
28 TCP中 TIME_WAIT状态 造成的影响
在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。
如发现系统存在大量TIME_WAIT状态的连接,通过调整内核参数解决
https://www.cnblogs.com/williamjie/p/9500592.html
29 一个很大的文件,里面有很多整数,怎么排序
我们可以将一个很大的文件,切分成很多个小文件,使得每个小文件能够单独的装进内存,并将每个小文件进行内排序(快速排序等等),然后再将多个小文件进行多路归并排序,最终得到一个有序的文件。
https://blog.csdn.net/qq_23160237/article/details/87862619
30 UDP实现简单的消息发送功能
客户端的步骤:
1.建立一个Socket
2.建个数据包
3. 发送数据包
4.关闭资源
服务器端:
1 开启端口,准备接受数据
2.接受相应的数据包
3. 对数据包中的数据进行处理
4. 关闭资源
https://blog.csdn.net/weixin_44159171/article/details/109962206
31 虚拟内存和物理内存
虚拟内存
虚拟内存是操作系统为每个进程提供的一种抽象,每个进程都有属于自己的、私有的、地址连续的虚拟内存,当然我们知道最终进程的数据及代码必然要放到物理内存上,那么必须有某种机制能记住虚拟地址空间中的某个数据被放到了哪个物理内存地址上,这就是所谓的地址空间映射,那么操作系统是如何记住这种映射关系的呢,答案就是页表。
每个进程都有自己的虚拟地址空间,进程内的所有线程共享进程的虚拟地址空间。
32 进程切换与线程切换的区别
系统中的每个程序都是运行在某个进程的上下文中的。
上下文是由程序正确运行所需的状态组成的,这个状态包括存放在存储器中的程序的代码和数据,他的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。
所以进程切换就是上下文切换。
进程切换简单理解可以认为是切换页表。
进程切换与线程切换的一个最主要区别就在于进程切换涉及到虚拟地址空间的切换而线程切换则不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
为什么虚拟地址空间切换会比较耗时呢?
进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个cache就是TLB(translation Lookaside Buffer,我们不需要关心这个名字只需要知道TLB本质上就是一个cache,是用来加速页表查找的)。由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢,而线程切换则不会导致TLB失效,因为线程线程无需切换地址空间,因此我们通常说线程切换要比较进程切换块,原因就在这里。
https://www.cnblogs.com/lfri/p/12597297.html
33 epoll为什么使用红黑树
epoll和poll的一个很大的区别在于,poll每次调用时都会存在一个将pollfd结构体数组中的每个结构体元素从用户态向内核态中的一个链表节点拷贝的过程,而内核中的这个链表并不会一直保存,当poll运行一次就会重新执行一次上述的拷贝过程,这说明一个问题:poll并不会在内核中为要监听的文件描述符长久的维护一个数据结构来存放他们,
而epoll内核中维护了一个内核事件表,它是将所有的文件描述符全部都存放在内核中,系统去检测有事件发生的时候触发回调,当你要添加新的文件描述符的时候也是调用epoll_ctl函数使用EPOLL_CTL_ADD宏来插入,epoll_wait也不是每次调用时都会重新拷贝一遍所有的文件描述符到内核态。当我现在要在内核中长久的维护一个数据结构来存放文件描述符,并且时常会有插入,查找和删除的操作发生,这对内核的效率会产生不小的影响,因此需要一种插入,查找和删除效率都不错的数据结构来存放这些文件描述符,那么红黑树当然是不二的人选。
https://zhuanlan.zhihu.com/p/366955699
34 单例模式的应用场景
-
Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
-
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
-
网站的计数器,一般也是采用单例模式实现,否则难以同步。
-
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
-
Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
-
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
-
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
-
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
-
HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
总结以上,不难看出:
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
https://www.jianshu.com/p/29a90d177d21
35 怎么使用UDP实现可靠传输,或者使用UDP实现HTTP(就是HTTP3)
传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现确认机制、重传机制、窗口确认机制
目前有如下开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT。
1、RUDP(Reliable User Datagram Protocol)
RUDP 提供一组数据服务质量增强机制,如拥塞控制的改进、重发机制及淡化服务器算法等
2、RTP(Real Time Protocol)
RTP为数据提供了具有实时特征的端对端传送服务,如在组播或单播网络服务下的交互式视频音频或模拟数据。
3、UDT(UDP-based Data Transfer Protocol)
基于UDP的数据传输协议(UDP-basedData Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。
36 线程数我们一般设多少比较合理呢?
总的来说,线程数量太多太少其实都不太好,多了会因为线程频繁切换导致开销增大,有时候反而降低了系统性能。少了又会导致CPU资源不能充分的利用起来,性能没有达到瓶颈。
所以,系统到底使用多少线程合适,是要看系统的线程是否能充分的利用了CPU
其实上面我们也提到过,也就是两种计算特性:
CPU密集型: 因为每个CPU都是高计算负载的情况,如果设置过多的线程反而会产生不必要的上下文切换。所以,一般线程我们会设置 CPU 核数 + 1就可以了,为啥要加1 呢,即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费,其实就是个备份。
**IO密集型:**因为大量的IO操作,会导致CPU处于空闲状态,所以这时我们可以多设置些线程。 所以, 线程数 = CPU 核心数 * (1+ IO 耗时/CPU 耗时) 就可以了,希望能给你点启发。
37 拷贝构造函数的参数能用值传递吗?
由函数的传参过程我们可以得知,如果函数的参数不是指针或者引用的时候,我们在传参的时候,会将实参复制给形参。
如果我们将拷贝构造函数的参数去掉引用,那么在调用拷贝构造函数的时候,首先会申请一个新的对象,然后用传入的实参去初始化这个新的对象,这个时候还会调用到我们的拷贝构造函数,如此层层调用,形成无限的递归。
因此,拷贝构造函数的参数必须用引用或者指针,不能采用值传递。
38 shared_ptr是怎么实现引用计数
https://blog.csdn.net/qq_29108585/article/details/78027867
https://www.jianshu.com/p/b6ac02d406a0
39 redis的持久化机制
Redis提供了RDB持久化和AOF持久化
RDB机制的优势和略施
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。
也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置
save 900 1 #900秒内如果超过1个key被修改,则发起快照保存
save 300 10 #300秒内容如超过10个key被修改,则发起快照保存
save 60 10000
AOF持久化(Append Only File):
则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
https://www.cnblogs.com/xingzc/p/5988080.html
40 linux写时复制(copy-on-write)
在Linux程序中,fork()
会产生一个和父进程完全相同的子进程,但子进程在此后多会使用exec
系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
写时复制技术:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。
https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html
41 说说redis的rdb原理。假设服务器的内存8g,redis父进程占用了6g,子进程fork父进程后,子父进程总共占用内存12g,如何解决内存不足的问题?(挖)
这是一道挖坑题。内存占用只会比6g多一点,不会用12g。
RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。
核心知识点:
fork的子进程与父进程共享内存空间,这里有个内存管理上“写时复制”的点,需深究操作系统进程管理的实现了!
42 mysql三种日志 binlog,undolog,redolog
bin log、redo log、undo log三种日志属于不同级别的日志,按照mysql的划分可以分为服务层和引擎层两大层,bin log是在服务层实现的;redo log、undo log是在引擎层实现的,且是innodb引擎独有的,主要和事务相关。
bin log
bin log中记录的是整个mysql数据库的操作内容,对所有的引擎都适用,包括执行的DDL、DML,可以用来进行数据库的恢复及复制。bin log有三种形式:statement、row、mixed,statement是基于语句的,也就是执行的sql语句,该种形式的文件比较小,例,update t1 set age=‘24’ where name like ‘%王%’,这样一条语句,在statement下就会记录这样一条sql;row是基于数据行的,会记录变化的所有数据,一般文件较大。例,update t1 set age=‘24’ where name like ‘%王%’,这条语句,在row的形式下,则会记录该条sql影响的所有数据记录;mixed是混合格式,是statement和row的组合;
redo log
redo log中记录的是要更新的数据,比如一条数据已提交成功,并不会立即同步到磁盘,而是先记录到redo log中,等待合适的时机再刷盘,为了实现事务的持久性
undo log
undo log中记录的是当前操作中的相反操作,一条insert语句在undo log中会对应一条delete语句,update语句会在undo log中对应相反的update语句,在事务回滚时会用到undo log,实现事务的原子性,同时会用在MVCC中,undo中会有一条记录的多个版本,用在快照读中;