c++知识补充

一,5种io模型
1,阻塞io模型
程序进行io操作,会一直阻塞等待,直到操作完成才返回结果。
优点:操作简单
缺点:导致cpu空闲等待,效率较低
2,非阻塞io模型
程序在进行io操作时,先判断io设备是否就绪,如果未就绪,则返回错误信息。程序可以继续执行其他操作,等一段时间过后,再来判断io设备是否就绪,知道设备就绪后,才进行io操作。
优点:减少CPU空闲等待
缺点:需要多次查询,效率较低
3,多路复用io模型
io多路复用允许单线程/进程同时监测多个io流的状态,当其中任意一个流发生变化 ,操作系统会通知该线程/进程进行处理。
io多路复用的实现一般由操作系统提供的select,poll,epoll(libevent是对epoll、poll和select进行了封装)等系统调用来实现。

特别强调的是:
io多路复用模型其实是同步io。
select,poll,epoll实际上是“伪异步”,他们表面上实现了异步,但其实他们只是将io操作交给了内核,让内核去帮他们处理io操作,让他们看上去是异步的,其实本质还是同步的。当一个io操作被调用,程序还是会一直等待io操作的完成,只不过这个过程交给了内核,程序员没有看到。

还有就是epoll_wait函数,在调用了epoll_wait()函数后,
如果他的超时参数设置为-1(超时参数是epoll_wait的最后一个参数),
在没有任何文件描述符就绪的情况下,程序会被阻塞,即暂停执行,直到至少有一个文件描述符就绪并可以进行读写操作时,epoll_wait()函数才会返回,并且返回就绪的文件描述符数量。此时,程序才可以继续执行下一步操作。
如果他的超时参数设置为0,
在没有事件发生的情况下,epoll_wait会立即返回,不会阻塞程序的运行


4,信号驱动io模型
使用操作系统的提供的信号机制来通知程序io设备是否准备就绪。程序在处理io操作之前,先向操作系统注册信号处理函数,在io设备就绪后,操作系统会向程序发送信号,程序收到信号后,再执行想应的处理函数。
优点:提高系统性能和效率。

5,异步io模型
异步io模型允许程序在进行了io操作后,继续执行其他操作,当io设备就绪后,操作系统通知程序进行io操作,程序在进行io操作时,不需要进行查询操作,直接读取或写入数据。
这种模型可以减少CPU空闲等待,提高性能和效率。

二,详细说说io多路复用?
io多路复用允许单线程/进程同时监测多个io流的状态,当其中任意一个流发生变化 ,操作系统会通知该线程/进程进行处理。
io多路复用的实现一般由操作系统提供的select,poll,epoll(libevent是对epoll、poll和select进行了封装)等系统调用来实现。
当进程调用select和poll时,操作系统

三,select和epoll的区别?
1,select是经典的io多路复用模型,epoll是新的io多路复用模型。
2,select最大可以处理的文件描述符数量有限,一般是1024,而epoll没有数量限制,可以处理任意数量的文件描述符。
3,select采用的轮询的方式依次检查每个文件描述符的状态,这种方式效率比较低下;而epoll采用的是事件通知的方式,只对发生事件的文件描述符进行操作,效率则是更高。
4,select和epoll的内核和用户空间的交互方式不同。

select是传统的I/O多路复用机制,它使用一个fd_set类型的数据结构,将所有需要监视的文件描述符放入其中,然后调用select函数,该函数将阻塞直到任何一个文件描述符就绪,或者超时。在内核中,select会遍历所有传递给它的文件描述符,以确定哪些文件描述符已经就绪,复杂度为O(N),然后将结果返回给用户空间。这种方式的缺点是,随着文件描述符数量的增加,select的性能会下降,因为每次调用select时,内核都需要遍历整个fd_set集合。

而epoll使用了更加高效的机制来解决这个问题。epoll使用一个红黑树来存储所有监视的文件描述符,这个红黑树可以快速的查找已经就绪的文件描述符,复杂度为O(logN)。当文件描述符就绪时,内核会将就绪的文件描述符添加到一个链表中,并通知用户空间,用户空间可以使用epoll_wait函数来获取这个链表中的就绪文件描述符。这种方式的好处是,epoll可以监视大量的文件描述符,而且在文件描述符数量较大时,性能不会下降。

四,select和poll的区别?
select和poll都是io多路复用的机制,用于监听多个文件描述符上的事件。他们的区别主要是在于文件描述符数量的上限,数据结构,效率,可移植性,支持的io类型等方面。
1,select可以监听的文件描述符数量的上限是1024,而poll没有限制
2,select可以跨平台,但是poll只能在Linux系统下运行。
3,select和poll只能设置水平触发,不能设置边缘触发,而epoll既可以设置水平触发,又可以设置边缘触发(水平触发:只要有数据没有被完全读取或写入,就会一直通知你。边缘触发:内核只通知一次。)。
4,select() 使用三个 fd_set 类型的文件描述符集合(读,写,异常)来描述所有需要监听的文件描述符,而 poll() 使用一个 pollfd 结构体数组来描述所有需要监听的文件描述符。
5,select() 在返回时需要轮询所有文件描述符集合,而 poll() 只需要轮询传入的文件描述符数组。
6,select() 的文件描述符集合是输入输出参数,需要在每次调用 select() 前都要重新设置,而 poll() 的文件描述符数组是一个输入参数,不需要重新设置。
7,select() 机制支持除了文件描述符外还支持其他类型的 I/O,如管道、信号等,而 poll() 只支持文件描述符类型的 I/O。

五,同步io和异步io的区别?
1,同步io和异步io主要区别是在进行io操作时,是否要等待操作完成。
2,同步io就是程序在执行一个io操作时,需要等待这个io操作完成才能进行下一步操作。
优点就是操作简单,缺点就是会等待io操作的完成,造成程序的阻塞
3,异步io就是在执行一次io操作后,不用等待这个io操作的完成,立即就可以进行下一步的操作。当io操作完成时,系统再通知程序去处理io操作的结果(select,poll,epoll都可以实现异步io,但他们不是异步io模型,而是多路复用io模型)。
优点是可以提高程序的并发量和吞吐量,缺点是编程复杂,需要调用回调函数。

六,epoll为什么相对于poll和select更加高效?
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能睡眠和唤醒多次交替。而epoll只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。
(2)select,poll “每次”调用都要把fd集合从用户态往内核态拷贝一次,并且要把当前进程往设备等待队列中加入一次,
而epoll只要一次拷贝,而且把当前进程往等待队列上加入也只加入一次,这也能节省不少的开销。


(在使用 select 和 poll 等函数进行 I/O 多路复用时,内核需要维护一个等待队列,用于存储所有等待 I/O 事件的进程。当某个文件描述符上有事件发生时,内核会将等待该事件的进程挂起(或者说加入)到等待队列中,并且将该文件描述符对应的事件状态设置为已就绪。当所有的文件描述符都被处理完毕后,内核会将所有已就绪的进程从等待队列中唤醒,并且返回就绪的文件描述符和对应的事件类型。

在这个过程中,每次调用 select 和 poll 等函数时,都需要将当前进程挂起(或者说加入)到等待队列中,以便内核能够在事件就绪时通知进程。因此,每次调用 select 和 poll 等函数时,都需要将当前进程从用户态切换到内核态,并且将当前进程挂起到等待队列中。这个过程是比较耗费资源的,尤其是当需要监听的文件描述符数量比较多时,会导致系统性能下降。

相比之下,epoll 使用了一种不同的机制,可以避免将进程挂起到等待队列中。在 epoll 中,内核会维护一个就绪链表,用于存储所有已就绪的文件描述符。当某个文件描述符上有事件发生时,内核会将该文件描述符对应的事件状态设置为已就绪,并且将该文件描述符添加到就绪链表中。当进程调用 epoll_wait 函数时,内核会检查就绪链表是否为空,如果不为空,则将所有已就绪的文件描述符从就绪链表中取出,并且返回给进程。由于不需要将进程挂起到等待队列中,因此 epoll 可以避免每次调用函数时的进程挂起和唤醒操作,从而提高了系统性能。)


七,epoll是最快的么,什么场景下

不一定。epoll的一个缺点,当事件触发比较频繁时,回调函数也会被频繁触发,此时效率就未必比select 或 poll高了。所以epoll的最佳使用情景是:连接数量多,但活跃的连接数量少。


八,epoll是如何操作fd的

epoll提供了三个函数,epoll_create、epoll_ctl和epoll_wait。 首先创建一个epoll对象,然后使用epoll_ctl对这个对象进行操作(添加、删除、修改),把需要监控的描述符加进去,这些描述符将会以epoll_event结构体的形式组成一颗红黑树,接着阻塞在epoll_wait,进入大循环,当某个fd上有事件发生时,内核将会把其对应的结构体放入一个链表中,返回有事件发生的链表。

九,怎么实现虚函数(虚表、虚指针)

C++实现虚函数的原理是虚函数表+虚表指针。

当一个类里存在虚函数时,编译器会为类创建一个虚函数表,虚函数表是一个数组,数组的元素存放的是类中虚函数的地址。

同时为每个类的对象添加一个隐藏成员,该隐藏成员保存了指向该虚函数表的指针。该隐藏成员占据该对象的内存布局的最前端。

所以虚函数表只有一份,而有多少个对象,就对应多少个虚函数表指针。

十,模板展开在哪个阶段

编译阶段

十一,用函数模板写过哪些功能
比如,快排?(待补充)

十二,裸socket连接流程

(1)服务器根据地址类型( ipv4, ipv6 )、 socket 类型、协议创建 socket。

(2)服务器为 socket 绑定 IP 地址和端口号。

(3)服务器 socket 监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket 并没有被打开 。

(4)客户端创建 socket。

(5)客户端打开 socket,根据服务器 IP 地址和端口号试图连接服务器 socket。

(6)服务器 socket 接收到客户端 socket 请求,被动打开,开始接收客户端请求,直到客户端返回连接信息 。这时候 socket 进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端连接请求 。

(7)客户端连接成功,向服务器发送连接状态信息 。

(8)服务器 accept 方法返回,连接成功 。

(9)客户端向 socket 写入信息 。

(10)服务器读取信息 。

(11)客户端关闭 。

(12)服务器端关闭 。
十三,c++新特性有哪些

C++后续版本更是发展了不少新特性,如C++11中引入了nullptr、auto变量、Lambda匿名函数、右值引用、智能指针。

十四,为什么用智能指针

使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等。

正是因为指针存在这样的问题,C++便引入了智能指针来更好的管理堆内存。智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。

因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,自动释放资源。这样程序员就不用再担心内存泄露的问题了。

C++里面有四个指针:auto_ptr、unique_ptr、shared_ptr、weak_ptr,后面三个是C++11支持的,第一个被C++11弃用。

十五,怎么设计share_ptr,引用计数存在哪里

实现原理:有一个引用计数的指针类型变量,专门用于引用计数,使用拷贝构造函数和赋值拷贝构造函数时,引用计数加1,当引用计数为0时,释放资源。

十六,全用share_ptr就能解决内存泄漏了么?

会出现内存泄露问题。

共享指针的循环引用计数问题:当两个类中相互定义shared_ptr成员变量,同时对象相互赋值时,就会产生循环引用计数问题,最后引用计数无法清零,资源得不到释放。

可以使用weak_ptr,weak_ptr是弱引用,weak_ptr的构造和析构不会引起引用计数的增加或减少。我们可以将其中一个改为weak_ptr指针就可以了。比如我们将class B里shared_ptr换成weak_ptr。

十七,weak_ptr的lock()函数怎么知道share_ptr是否存在

lock()方法的功能是:判断weak_ptr所指向的shared_ptr对象是否存在。若存在,则这个lock方法会返回一个指向该对象的shared_ptr指针;若它所指向的这个shared_ptr对象不存在,则lock()函数会返回一个空的shared_ptr。

十八,静态变量初始化顺序

对于C语言的全局和静态变量,初始化发生在任何代码执行之前,属于编译期初始化。

而C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造。

十九,写一个线程池(待补充)


二十,malloc种brk和mmp的区别
二十一,写一个单例模式(分别实现懒汉式与饿汉式)
全部评论
快排函数模板 #include <iostream> #include <vector> template <typename t=""> void quicksort(std::vector<t>& v, int left, int right) { if (left < right) { int i = left, j = right; T pivot = v[(left + right) / 2]; while (i <= j) { while (v[i] < pivot) i++; while (v[j] > pivot) j--; if (i <= j) { std::swap(v[i], v[j]); i++; j--; } } quicksort(v, left, j); quicksort(v, i, right); } } int main() { std::vector<int> v = {9, 3, 6, 1, 8, 4, 7, 5, 2}; quicksort(v, 0, v.size()-1); for (auto i : v) { std::cout << i << " "; } std::cout << std::endl; return 0; }</int></t></typename></vector></iostream>
点赞
送花
回复
分享
发布于 2023-07-22 15:38 河北
malloc中brk和mmap的区别: 在 malloc 函数内部,它会通过系统调用向操作系统请求一块连续的虚拟内存空间, 并将这个虚拟内存映射到进程的地址空间中。在 Linux 系统中,这个映射的过程涉及到两个系统调用:brk 和 mmap。 brk 和 mmap 都是 Linux 系统中用于管理进程虚拟内存空间的系统调用。 它们的主要区别在于, brk 是将进程的数据段(data segment)调整到指定的地址, 而 mmap 是创建一个新的虚拟内存映射区域。 在 malloc 函数内部, 如果要分配的内存大小较小(通常小于 128KB),则会使用 brk 系统调用向数据段中添加一个新的内存区域, 否则会使用 mmap 系统调用创建一个新的虚拟内存映射区域。 总的来说,brk 和 mmap 都是 Linux 系统中用于管理进程虚拟内存空间的系统调用,它们的主要区别在于 brk 是将当前数据段末尾指针向后移动,而 mmap 是创建一个新的虚拟内存映射区域。
点赞
送花
回复
分享
发布于 2023-07-22 15:58 河北
秋招专场
校招火热招聘中
官网直投
是的,epoll既是一种异步I/O机制,也是一种多路复用I/O机制。 作为多路复用I/O机制,epoll允许应用程序同时监视多个文件描述符的状态,并且在其中任何一个文件描述符就绪时返回。这使得应用程序可以在等待I/O事件的同时继续执行其他任务,而不必等待I/O操作完成。 作为异步I/O机制,epoll允许应用程序在等待I/O操作完成时继续执行其他任务,而不会被阻塞。当应用程序调用epoll_wait函数时,它会等待一个或多个文件描述符上的I/O事件发生,并在事件发生时返回。这使得应用程序可以在等待I/O事件的同时继续执行其他任务,而不必等待I/O操作完成。 因此,epoll既具有多路复用I/O机制的优点,又具有异步I/O机制的优点,具有更高的效率和更好的扩展性,被广泛应用于高并发的网络编程中。
点赞
送花
回复
分享
发布于 2023-08-03 20:38 河北

相关推荐

点赞 评论 收藏
转发
11 39 评论
分享
牛客网
牛客企业服务