科大讯飞--C++开发工程师一面

  1. 多线程实现的思路
    线程池是由服务器预先创建的一组子线程,线程池中的线程数量应该和CPU数量差不多线程池中的所有子线程都运行着相同的代码。当有新的任务到来时,主线程将通过某种方式选择线程池中的某一个子线程来为之服务。相比于动态的创建子线程,选择一个已经存在的子线程的代价显然要小很多。
    主线程使用某种算法主动选择子线程;
    主线程和所有子线程通过一个共享工作队列来同步,子线程都睡眠在该工作队列上。当有新的任务到来时,主线程将任务添加到工作队列中。这将唤醒正在等待任务的子线程,不过只有一个子线程将获得新任务的“接管权”,他可以从工作队列中取出任务并执行之,而其他子线程将继续睡眠在工作队列上。
  2. 信号量和互斥量的区别
    信号量(多线程同步使用的):一个线程完成某个动作后通过信号量告诉别的线程,别的线程才可以执行某些动作。
    互斥量(多线程互斥使用的):一个线程占用某个资源,那么别的线程就无法访问,直到该线程离开,其他线程才可以访问该资源。
    区别:
    1)互斥量用户线程的互斥,信号量用户线程的同步;
    2)互斥量只能是0/1,信号量值可以为非负值
    3)互斥量的加锁和解锁必须由同一个线程分别对应使用;而信号量可以由一个线程释放,另外一个线程得到。
  3. 多线程相对于单线程有什么缺点?
    增加了调度和管理的开销,带来了一些不确定性,需要复杂的同步机制,避免死锁等等。
    时间成本(每次切换进程或者线程所耗费的时间);
    内存成本(每个线程或者进程都需要占用一定的内存);
  4. 为什么要选择epoll,而不用select或者poll
    select本质上是通过设置或者检查存放fd标志位的数据结构数据结构来进行下一步的处理,时间复杂度:O(n)
     缺点:
      1) 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;
      2) 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;
      3) 单个进程可监视的fd数量被限制;
      4) 对socket进行扫描是线性扫描;
    优点:
      1)select的可移植性更好,在某些Unix系统上不支持poll()。
      2)select对于超时值提供了更好的精度:微秒,而poll是毫秒。
      poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待对垒中加入一项继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,知道设备就绪或者主动超时,被唤醒后它又要再次遍历fd这个过程经理了多次无谓的遍历。时间复杂度O(n)
    缺点:
      1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义;
      2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
    优点:
      1)poll() 不要求开发者计算最大文件描述符加一的大小。
      2)poll() 在应付大数目的文件描述符的时候速度更快,相比于select。
      3)它没有最大连接数的限制,原因是它是基于链表来存储的。
      epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))
    缺点:
      1)相对select来说, epoll的跨平台性不够用 只能工作在linux下, 而select可以在windows linux apple上使用。
      2)相对select来说 还是用起来还是复杂了一些, 不过和IOCP比起来 增加了一点点的复杂度却基本上达到了IOCP的并发量和性能, 而复杂度远远小于IOCP
      3)相对IOCP来说 对多核/多线程的支持不够好, 性能也因此在性能要求比较苛刻的情况下不如IOCP.
    优点:
      1)IO效率不随FD数目增加而线性下降:epoll不存在这个问题,它只会对"活跃"的socket进行操作。
      2)使用mmap加速内核与用户空间的消息传递:epoll通过内核和用户空间共享一块内存来实现的。
    请添加图片描述
  5. 网络编程中bind失败有哪些原因
    bind需要root权限;
    端口被占用(或者bind 普遍遭遇的问题是试图绑定一个已经在使用的port。该陷阱是或许没有活动的套接字存在,但仍然禁止绑定port(bind 返回EADDRINUSE)。它由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟。)
  6. tcp相对于udp而言有什么缺点?
    慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。
    每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  7. 四次挥手为什么不能是3次?
    关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
    服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
    从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。
  8. 数据库的事务特性?
    原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态;
    一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。
    隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间
    持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
  9. 在C++一般使用const定义常量来代替define,为什么?
    C++中应避免使用#define,因#define没有作用域的概念,因此会破坏C++的封装性;另一方面,预处理器会在编译前将替换#define定义的常量,程序中没有了定义的标识符,会影响debug或crash问题的查找;另外被常量值替换了的常量出现在多处,就可能在多个位置存储,增加程序体积。C++中可使用const代替#define,避免上述问题。
  10. C++为什么不推荐在构造函数和析构函数中调用虚函数?
    1)构造函数
    在概念上,构造函数的工作是为对象进行初始化。在构造函数完成之前,被构造的对象被认为“未完全生成”。当创建某个派生类的对象时,如果在它的基类的构造函数中调用虚函数,那么此时派生类的构造函数并未执行,所调用的函数可能操作还没有被初始化的成员,最终导致灾难的发生。
    即使想在构造函数中实现动态联编,在实现上也会遇到困难。这涉及到对象虚指针(vptr)的建立问题。在Visual C++中,包含虚函数的类对象的虚指针被安排在对象的起始地址处,并且虚函数表(vtable)的地址是由构造函数写入虚指针的。所以,一个类的构造函数在执行时,并不能保证该函数所能访问到的虚指针就是当前被构造对象最后所拥有的虚指针,因为后面派生类的构造函数会对当前被构造对象的虚指针进行重写,因此无法完成动态联编。
    2)析构函数
    析构函数是用来销毁一个对象的,在销毁一个对象时,先调用该对象所属类的析构函数,然后再调用其基类的析构函数,所以,在调用基类的析构函数时,派生类对象的“善后”工作已经完成了,这个时候再调用在派生类中定义的函数版本已经没有意义了。
  11. 析构函数为什么通常定义为虚函数?为什么?
    虚析构或纯虚析构就是用来解决通过父类指针释放子类对象,如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。
    多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
  12. 多态的几种实现?
    多态分为两类:
    1)静态多态:函数重载和运算符重载属于静态多态,复用函数名
    2)动态多态:派生类和虚函数实现运行时多态
  13. i++和++i其中一个作为左值,分析一下?
    i++就是先用了i的值(用于赋值之类的操作),再i自加1. 而++i就是把i的值先自加1再用作其他操作。
    左值就是能够出现在赋值符号左面的东西,而右值就是那些可以出现在赋值符号右面的东西。
    如:
    i++=5; 是错误的是因为i++返回的是编译器自动分配的临时变量temp,而这个temp并不是你程序中定义的可寻址变量的引用,也就是说你不能通过地址对它进行操作.(换句话说就是不能作为左值)。++i=5;是正确的就是因为其返回值就是i;
  14. 程序编译的几个阶段
    预处理(-E)
    预处理就是对源程序中的伪指令(以#开头的指令)和特殊符号进行处理的过程。伪指令包含宏定义指令,条件编译指令和头文件包含指令。gcc对C源文件进行预处理后会输出 .i 文件。
    主要处理规则如下:
    (1)将所有#define删除,并且展开所有的宏定义。
    (2)处理所有条件编译指令。如#if、#ifdef等
    (3)处理#include预编译指令,将被包含的文件插入该预编译指令的位置。
    (4)删除所有的注释
    (5)添加行号和文件标识,以便于编译时编译器产生调试用的行号信息及编译时产生编译错误或警告时能够显示行号信息
    (6)保留所有的#pragma编译器指令,因为编译器需要使用它们。
    编译(-S)
    编译就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码。使用gcc进行编译时,默认情况下,不输出该文件,生成的汇编文件是 .s 文件
    汇编(-c)
    汇编就是将汇编代码转变为机器可以执行的二进制代码,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
    链接
    在成功汇编之后,就进入链接阶段。链接主要是为了解决多个文件之间符号引用的问题。编译时编译器只对单个文件进行处理,如果该文件里面需要引用到其他文件中的符号,那么这时在这个文件中该符号的地址是没法确定的,只能等链接器把所有的目标文件链接到一起,才能确定最终的地址,最终生成可执行文件。
    在这里插入图片描述
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZhInen丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值