查漏补缺

2019-03-17  本文已影响0人  Supreme_DJK

多态: 静态多态(重载)、动态多态(虚函数)

B树的优点: 由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。

数据库索引采用B+树的主要原因是B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。正是为了解决这个问题,B+树应运而生。B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作(或者说效率太低)。

栈作用可以从两个方面体现:函数调用 和 多任务支持 。

hash_set/hash_map:

底层是有hash_table所封装实现的,hash_table(开链法解决冲突)的底层是由vector实现的,set/map底层是由RB tree实现的。

  1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;

  2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线

  3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信 号等),某进程内的线程在其他进程不可见;

  4. 调度和切换:线程上下文切换比进程上下文切换要快得多

进程独占的资源:地址空间、全局变量、打开的文件、子进程、信号量、账户信息

线程占有的资源:栈、寄存器、状态、程序计数器

  • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
  • 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程

线程执行开销小,但是不利于资源的管理和保护。线程适合在SMP机器(双CPU系统)上运行。 进程执行开销大,但是能够很好的进行资源管理和保护。进程可以跨机器前移。

对资源的管理和保护要求高,不限制开销和效率时,使用多进程。

要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。

  1. 引⽤只能在定义时初始化⼀次,之后不能改变指向其它变量(从⼀⽽终);指 针变量的值可变。

  2. 引⽤必须指向有效的变量,指针可以为空。

  3. sizeof指针对象和引⽤对象的意义不⼀样。 sizeof引⽤得到的是所指向的变量的 ⼤⼩,⽽sizeof指针是对象地址的⼤⼩。

  4. 指针和引⽤⾃增(++)⾃减(--)意义不⼀样。

  5. 相对⽽⾔,引⽤⽐指针更安全。

image

slab 分配器:slab根据对象进行管理的,当申请一个对象时,从slab列表中寻找一个大小合适的单元出去,释放时,将其重新保存在该列表中,从而避免内部碎片。

随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。

限流算法

常见的限流算法有:令牌桶(允许一定程度的突发量)、漏桶(平滑突发流入速率)。计数器(限制总并发数)也可以进行粗暴限流实现。

  1. 绑定的对象(引用的对象)不同,左值引用绑定的是返回左值引用的函数、赋值、下标、解引用、前置递增递减

  2. 左值持久,右值短暂,右值只能绑定到临时对象,所引用的对象将要销毁或该对象没有其他用户

  3. 使用右值引用的代码可以自由的接管所引用对象的内容 右值引用应用:移动赋值函数和移动构造函数,减少了拷贝赋值和拷贝构造的开销。


Const引用 image

僵尸进程:

父进程未回收(wait/waitpid)退出到的子进程,子进程退出时仍然保存了一定的信息(进程ID,退出状态,运行时间),这样,大量的进程号被占用。

孤儿进程:

父进程退出了,它的子进程还在运行,这些子进程将由init进程回收。

守护进程:

运行在后台的特殊进程。独立于终端并且周期性地执行某种任务或等待处理的时间。Linux下的大多数服务器都是利用守护进程实现的

信号

PS命令:进程的快照

内存泄漏会导致内存溢出

类模板偏特化:

特例化本质上是我们顶替了编译器的工作,我们帮编译器做了类型推导

  1. 第一级配置器 直接调用malloc和free来配置和释放内存,如果配置区块大于128bytes,会调用第一级配置器

  2. 第二级配置器 (内存池) 配置区块小于等于128bytes时,使用第二级配置器。第二季配置器采用自由链表+内存池进行管理。自由链表类似于hash桶,它的大小为16,每个位置代表区别大小,范围从8bytes-128bytes,其中大小都为8bytes的倍数。如果用户需要是一块n字节的区块,且n <= 128(调用第二级配置器),此时Refill填充是这样的:(需要注意的是:系统会自动将n字节扩展到8的倍数也就是RoundUP(n),再将RoundUP(n)传给Refill)。用户需要n块,且自由链表中没有,因此系统会向内存池申请20 * n大小的内存块,如果内存池大于 20 * n,那么直接从内存池中取出。如果内存池小于20 * n,但是比一块大小n要大,那么此时将内存最大可分配的块数给自由链表,并且更新最大分配块数x (x < 20)。如果内存池连一个区块的大小n都无法提供,那么首先先将内存池残余的零头给挂在自由链表上,然后向系统heap申请空间,申请成功则返回,申请失败则到自己的自由链表中看看还有没有可用区块返回,如果连自由链表都没了最后会调用一级配置器。 两层空间配置器解决了以下问题:

  1. 内存碎片的问题,自由链表所挂区块都是8的整数倍,因此当我们需要非8倍数的区块,往往会导致浪费,比如我只要1字节的大小,但是自由链表最低分配8块,也就是浪费了7字节,我以为这也就是通常的以空间换时间的做法,这一点在计算机科学中很常见。

  2. 我们发现似乎没有释放自由链表所挂区块的函数?确实是的,由于配置器的所有方法,成员都是静态的,那么他们就是存放在静态区。释放时机就是程序结束,这样子会导致自由链表一直占用内存,自己进程可以用,其他进程却用不了。

基于chunk的大小有以下几种bin:

  1. Fast bin :当用户释放一块不大于max_fast(默认值64B)的chunk的时候,会默认会被放到fast bins上。当需要给用户分配的 chunk 小于或等于 max_fast 时,malloc 首先会到fast bins上寻找是否有合适的chunk,

  2. Unsorted bin :当用户释放的内存大于max_fast或者fast bins合并后的chunk都会首先进入unsorted bin上。

  3. Small bin

  4. Large bin

ptmalloc 在开始时,若请求的空间小于 mmap 分配阈值(mmap threshold,默认值为 128KB)时,主分配区会调用 sbrk()增加一块大小为 (128 KB + chunk_size) align 4KB 的空间作为 heap。非主分配区会调用 mmap 映射一块大小为 HEAP_MAX_SIZE(32 位系统上默认为 1MB,64 位系统上默认为 64MB)的空间作为 sub-heap。

  1. 从静态存储区分配:此时的内存在程序编译的时候已经分配好,在程序的整个运行期间都存在。全局变量和static变量都在这里存储

  2. 栈区分配:相关代码执行时创建,执行结束时被自动释放。效率高,但是容量有限

  3. 堆区分配:动态内存分配

  1. UDP是面向报文的,发送方的UDP对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层,也就是说无论应用层交给UDP多长的报文,它统统发送,一次发送一个。而对接收方,接到后直接去除首部,交给上面的应用层就完成任务了。因此,它需要应用层控制报文的大小。 2.TCP是面向字节流的,它把上面应用层交下来的数据看成无结构的字节流来发送,可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着,TCP会根据当前网络的拥塞状态来确定每个报文段的大小。

explicit:防止内置类型隐式转换()

  1. 不要在构造函数和析构函数中调用虚函数,因为这种情况下的虚函数调用不会调用到外层派生类的虚函数

  2. 对象的虚函数表地址在对象的构造和析构过程中会随着部分类的构造和析构而发生变化,这一点应该是编译器实现相关的。

创建一个线程默认的状态是joinable, 如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码),所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid) 但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此,比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以在子线程中加入代码 pthread_detach(pthread_self()) 或者父线程调用 pthread_detach(thread_id)(非阻塞,可立即返回) 这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。

项目中封装了IO线程(EventLoopThread),IO线程调用threadFunc创建EventLoop对象,通知主线程已经创建完成。用一个main reactor创建EventLoopThreadPoo,在线程池(EventLoopThreadPool)中,创建初初始化了一个线程队列和任务队列。在线程池中将EventLoop(任务)和Thread(线程)绑定,将IO线程中调用线程函数返回的EventLoop对象封装到EventLoop池中,使用轮询的方式返回每个EventLoop。

df:磁盘使用情况 lsof -i port:查看端口占用(root权限) netstat -tunlp |grep 8000:查看8000端口占用的情况 alias:设置指令的别名 kill -9 25260 # 表示杀死25260进程(杀死占用指定端口的进程)

硬链接: 这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。

硬链接造成的:

软链接又叫符号链接,这个文件包含了另一个文件的路径名。可以是任意文件或目录,可以链接不同文件系统的文件。

  1. 接收缓冲区有数据,一定可读

  2. 对方正常关闭socket,也是可读

  3. 对于侦听socket,有新连接到达也可读=

  4. socket有错误发生,可读也可写

for i:=1 to n do swap(a[i], a[random(1,n)]);

for i:=1 to n do swap(a[i], a[random(i,n)]);

算法复杂度是O(n。。。),要研究下random的实现。

路由器:寻址,转发(依靠 IP 地址) 交换机:过滤,转发(依靠 MAC 地址)

我们可以看出这两者的主要工作就是转发数据,但是不同之处是,依靠的地址不同,这是一个根本区别!

路由器内有一份路由表,里面有它的寻址信息(就像是一张地图),它收到网络层的数据报后,会根据路由表和选路算法将数据报转发到下一站(可能是路由器、交换机、目的主机)

交换机内有一张MAC表,里面存放着和它相连的所有设备的MAC地址,它会根据收到的数据帧的首部信息内的目的MAC地址在自己的表中查找,如果有就转发,如果没有就放弃

类成员函数的地址

只用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。 显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分(虚函数指针和虚基类指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间

image

不允许空值引用

不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高,因为在使用引用之前不需要测试它的合法性。

链表顺序访问和数组顺序访问哪个快?

相较于链表,数组的地址在物理上连续的,所以CPU可以为其建立cpu缓存加速访问,然而链表就需要到去内存中读取。

mysql优化数据访问

  1. 减少请求数据量
  1. 减少服务器端扫描的行数

extern关键字

**extern一般是使用在多文件之间需要共享某些代码 extern "C":简而言之,为C++调用C代码提供支持,因为C语言不支持重载, 保证在extern "C"声明的函数按照C来编译

程序的执行过程

主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令,宏的替换,添加行号,保留所有的编译器指令。

编译过程所进行的是对预处理后的文件进行语法分析,词法分析,语义分析,符号汇总,然后生成汇编代码。

将汇编代码转成二进制文件,二进制文件就可以让机器来读取。每一条汇编语句都会产生一句机器语言。

将目标文件、启动代码、库文件链接成可执行文件的过程,这个文件可被加载或拷贝到存储器执行。

惊群问题

多进程或者多线程阻塞在一个事件上(如accept、epoll) accept:

其实在Linux2.6版本以后,内核内核已经解决了accept()函数的“惊群”问题,大概的处理方式就是,当内核接收到一个客户连接后,只会唤醒等待队列上的第一个进程或线程。所以,如果服务器采用accept阻塞调用方式,在最新的Linux系统上,已经没有“惊群”的问题了。

epoll:

Nginx中使用mutex互斥锁解决这个问题,具体措施有使用全局互斥锁,每个子进程在epoll_wait()之前先去申请锁,申请到则继续处理,获取不到则等待,并设置了一个负载均衡的算法(当某一个子进程的任务量达到总设置量的7/8时,则不会再尝试去申请锁)来均衡各个进程的任务量

项目中无惊群问题,无论是accept(主线程)还是epoll(每个线程)都只有一个线程阻塞在其上。

static

static全局函数

可以访问全局函数、static全局函数、全局变量

static全局变量
static成员变量、成员函数

使用static修饰的函数,除了可以访问static修饰的变量及函数,还有template模板里面定义的默认值:

template<typename T, int i=1>
class someComputing {
public:
    typedef volatile T* retType; // 类型计算
    enum { retValume = i + someComputing<T, i-1>::retValume }; // 数值计算,递归
    static void f() { std::cout << "someComputing: i=" << i << '\n'; }
};

多重继承

mmap

采用共享内存通信的一个显而易见的好处效率高,因为进程可以直接读写内存,而不需要任何数据的复制.对于向管道和消息队列等通信等方式,则需要在内核和用户空间进行四次的数据复制,而共享内存则只需要两次数据复制:一次从输入文件到共享内存区,另一个从共享内存区到输出文件.

mmap系统调用是的是的进程间通过映射同一个普通文件实现共享内存.普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write等操作.与mmap系统调用配合使用的系统调用还有munmap,msync等.

define 、const区别

一个32位的操作系统能同时运行多少个程序(进程)?

若物理地址也是32位,则内存最大位4GB,现代操作系统使用内存分页,常见体系结构一个页至少是4KB,一个程序至少使用一个page才能实现地址空间隔离,这么计算=》内存方面最多允许同时运行2^20个程序(进程)

在一个32位的操作系统下,一个进程可以开启多少个线程?

???一个进程默认是2G,而每个线程默认有1M的栈共建,理论上线程数最多是2048个。

内核空间

32位的系统,内核空间占1GB,高位0xC0000000到0xFFFFFFFF的1GB线性地址空间。 内核线性地址空间由所有进程共享,只有在进程陷入到内核态的时候,可以访问内核空间

可重入和线程安全

因此,线程安全函数不一定的是可重入函数,因为阻塞在共享区,无法进入一次新的函数调用。

可重入函数的条件:
1)在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量);

2)对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。

指针变量

得看定义的地方,若是函数中,指针变量分配在栈中,随着函数结束而销毁,但是指针变量的指向不会。

nullptr(C++11特性)

引入的目的:用来区分0和NULL
因为重载以下函数会出问题,NULL会被视为int类型
在不同的编译器上,有的会将NULL宏定义为0,有的会定义为
(void*(0))

void fun(int);
void fun(void *);
fun(NULL); //会调用void fun(int)

bss段存放未初始化的全局变量
data段(全局静态区):存放初始化的全局变量、常量、静态变量(无论初始化与否)

通过编译生成的可执行文件,可以判断

上一篇 下一篇

猜你喜欢

热点阅读