一,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的区别二十一,写一个单例模式(分别实现懒汉式与饿汉式)
点赞 11
评论 3
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务