本文主要是介绍【八股03.29】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
【八股03.29】
1.内存泄漏?野指针?悬挂指针?
什么是内存泄漏?
内存泄漏是指程序运行过程中申请的动态分配的堆内存没有被释放,造成系统内存浪费,运行速度减慢甚至崩溃等严重后果。
如何避免?
养成良好的编码习惯,及时释放动态分配的堆内存。
使用智能指针,让其自动释放。
使用内存泄漏检测工具,如Valgrind等。
什么是野指针?什么是悬挂指针?
野指针是未初始化的指针,指向随机的地址;
悬挂指针是指向已经释放的内存的指针。
double delete
是未定义行为
delete nullptr
没有影响
delete p
后,p变成一个悬挂指针,通常delete
后需要将p设为nullptr
2.什么是智能指针?智能指针有哪些?
智能指针是C++11引入的一个类模板,用来帮我们自动管理资源,通常情况下,它内部封装了一个指向动态内存的裸指针,在析构函数它会自动释放此前申请的动态内存,防止我们因为忘记释放或发生异常导致的内存泄漏。
常用的有unique_ptr
, shared_ptr
, weak_ptr
,好像还有个auto_ptr
,已经废弃了。
3.说说这几种智能指针
unique_ptr
是一种独占式的智能指针,某一时刻只能有一个智能指针拥有资源的所有权,它不可以被拷贝,可以通过移动来交出资源的所有权。shared_ptr
是一种共享式的智能指针,它允许多个智能指针同时拥有资源的所有权,它内部会维护一个引用计数,当管理资源的智能指针+1时,内部的引用计数就会+1,在析构函数中会将引用计数-1,当引用计数为0时,就会释放资源。当返回this
指针的shared_ptr
和存在循环引用时,shared_ptr
可能会出现一些问题,weak_ptr
和shared_ptr
搭配使用的话就可以解决这些问题。weak_ptr
其实并不管理任何资源的生命周期,它也不会使得对应资源的引用计数增加,它一般和shared_ptr
一起搭配使用,作为一个旁观者观测shared_ptr
中管理的资源是否存在。比如循环引用中,只要将一个类中的shared_ptr
改为weak_ptr
就可以避免循环引用导致的死锁,因为当一个weak_ptr
指向一个资源时,对应shared_ptr
的引用计数不会+1。
4.shared_ptr的线程安全问题
shared_ptr
的线程安全问题主要包括3点
- 引用计数的加减操作是否线程安全?
shared_ptr
保证了引用计数的加减是一个原子操作,所以这个是线程安全的。 shared_ptr
修改指向时是否线程安全?(这个还没理解透,陈硕那本书里有)
这个要分两种情况讨论。当shared_ptr
通过引用和指针传递时,不同的线程操作的是同一个shared_ptr
此时是线程不安全的;当shared_ptr
通过值传递时,不同的线程操作的不是同一个shared_ptr
,不过指向的资源是相同的,此时是线程安全的。- 操作
shared_ptr
指向的数据时是否线程安全?
这个是由所指向的数据类型决定的,这个线程不安全不是由于shared_ptr
导致的,是因为这个数据类型自身就是线程不安全的。
5.有哪些IO模型?
- 阻塞IO
- 非阻塞IO
- IO复用
- 信号驱动IO
- 异步IO
6.什么是阻塞IO?什么是非阻塞IO?什么是同步IO?什么是异步IO?
一个典型的IO分为两阶段:数据准备和数据读写。
比如说一个recv
操作,会从网络中接收数据到内核缓冲区,这就是数据准备的过程,然后再从内核缓冲区读到用户空间,这就是数据读写的阶段。
阻塞IO和非阻塞IO主要说的是当数据没有准备好时,发起IO的线程的状态。
- 阻塞:当数据没准备好时,阻塞等待。
- 非阻塞:当数据没准备好时,不会阻塞等待,直接返回一个错误,可以通过
errno
判断出错还是数据没准备好。
同步IO和异步IO主要说的是是由内核还是应用程序来进行读写数据。
- 同步:由应用程序完成IO操作,在数据读写阶段发起IO操作的线程还是会阻塞,之后才会继续向下执行。
- 异步:交给操作系统完成IO操作,应用程序调用异步IO后立刻就可以继续往下执行,IO操作完成后会通知应用程序。
在处理IO的时候,阻塞和非阻塞都是同步IO,只有使用了特殊的API才是异步IO。
上述只是讨论了,在IO层面上的的阻塞非阻塞和同步异步,但是跳出IO这个范围,在大的层面上来说,感觉应该基本等于同义词。阻塞与非阻塞主要关注调用者等待调用结果时的一个状态,同步主要说线程必须完成前一个任务才能进行下一个,异步一般用在多线程中,当前线程开启一个任务可以去执行其它任务,可能由其它线程完成后再通知它。
7.说说IO多路复用?为什么要使用IO多路复用?IO多路复用的实现方式?
什么是IO多路复用?
IO多路复用是一种同步IO模型,多路是说多个网络连接,复用是指使用同一个线程,它使得一个线程可以同时监视多个文件描述符,阻塞直到监视的文件描述符处于就绪状态。
为什么需要IO多路复用?
如果不使用IO多路复用的话,假如有10000个网络连接,我们就要需要10000个线程去监视每个文件描述符,如果使用IO多路复用的话,就只需要一个线程来监视所有文件描述符,当有就绪的文件描述符时,再分配线程去进行处理,这样可以大大减少所需要的线程数量,因为线程创建和切换是需要开销了,这样能更加节省系统资源吧。
IO多路复用的实现方式?
IO多路复用主要有3中实现方式:select
, poll
, epoll
.
8.IO多路复用之select
使用select之前要先创建一个fd_set
结构,这个实际上就是一个数组,一共1024位,类似位图,每一位代表我们监听的一个文件描述符。当我们需要监听一个文件描述符时,通过select
提供的宏函数将对应的位置为1即可,调用select
时还需要传入一个参数就是最大文件描述符+1,因为内核是采取线性遍历的方式查看对应文件描述符上是否有事件发生。这个参数可以告诉内核停止遍历了。当有文件描述符就绪时,select
就会返回,fd_set
中对应的位仍然是1,没有事件发生的文件描述符对应的位会被置为0。select
会返回就绪的文件描述符总数,但是我们仍然需要遍历fd_set
来确定哪些文件描述符是就绪的。
缺点:
select
监视的文件描述符数量是有限的,也就是fd_set
的位数,这个在内核中是确定了的。- 每次调用
select
都要将fd_set
由用户态拷贝到内核态,开销比较大,效率比较低。 - 内核对
fd_set
中文件描述符的检测方式是线性扫描,效率比较低。
9.IO多路复用之poll
使用poll
的话我们要先对每个要监听的文件描述符创建一个pollfd
结构,这个结构体中成员有:被监听的文件描述符fd
,感兴趣的事件events
,实际发生的事件revents
,同时poll
还需要一个参数来指定pollfd
数组的大小。poll
会返回就绪的文件描述符总数,不过我们也需要遍历pollfd
数组来确定哪些文件描述符是就绪的。poll
和select
没有本质区别,它只解决了部分select
的问题,监听的文件描述符总数是没有限制的,但仍然有以下缺点。
缺点:
- 每次调用都要讲
pollfd
数组由用户态拷贝到内核态,效率比较低。 - 内核对文件描述符也是采用线性的方式查询,效率比较低。
10.IO多路复用之epoll
epoll
有3个API,epoll_cerate
创建一个epoll
实例,epoll_ctl
修改epoll
感兴趣的文件描述符,epoll_wait
监听文件描述符,阻塞直到有就绪的文件描述符。
epoll
是由红黑树和就绪链表实现的,epoll
通过红黑树管理文件描述符,不会采用线性方式扫描文件描述符,而是采用回调机制,当文件描述符就绪时,回调函数会将其放到就绪链表中,当就绪链表不为空时,epoll_wait
就会返回。同时epoll
采用mmap
的方式进行内核态和用户态的信息交换,避免了不必要的内存拷贝。epoll_wait
会返回已经就绪的文件描述符数量,而且传出参数evs
数组中只会有就绪的文件描述符对应的epoll_event
,不需要遍历所有文件描述符。
epoll
有两种工作模式:LT
(水平触发),ET
(边沿触发)。
水平触发是epoll
默认的工作模式,比如说对于读事件来说,如果是水平触发模式,那么只要读缓冲区还有数据,读事件就会一直被触发,epoll_wait
会直接返回;如果是边沿触发模式,那么只有当读缓冲区由空变为非空时,读事件才会触发,也就是说如果数据没有一次读完,读缓冲区还有数据,在边沿触发模式下,读事件是不会触发的,epoll_wait
不会返回。所以边沿触发模式一般要循环读,确保将数据一次读完,同时还要将文件描述符设置为非阻塞的,防止读操作无意义的阻塞,非阻塞读在读完数据后后返回errno
为EAGAIN
,通过这个可以判断数据是否已经读完。
这篇关于【八股03.29】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!