面试题目整理-涵盖操作系统、网络、算法、设计模式等

以下全部内容(包括网络、设计模式等)均已上传github,总结不易,请点击一个star,非常感谢

https://github.com/killsoiler/Hello-work-

关于图源无法查看问题,请直接访问github里面的pdf版

1. 进程、线程、协程

  1. 为什么使用线程:服务器为进程配置的资源太多,且切换开销较大,所以使用线程

  2. 进程和线程的区别

    1. 进程是操作系统资源分配的最小单位,线程是操作系统调度的最小单位

    2. 进程拥有独立的地址空间,在启动时分配,同时包含进程控制块、代码段、数据段、堆、栈

      线程的地址空间共享,仅拥有自己的线程栈

    3. 通信方式不同,线程共享全局变量、静态变量,共享更方便

      进程需要使用信号等等手段

    4. 进程间较为独立,进程崩溃一般不会影响其他进程

      线程崩溃,该进程的所有线程崩溃,因为线程共享地址空间

    5. 进程切换开销较大,线程切换开销较小

    6. 创建和撤销进程时,系统要为之分配/回收资源、内存空间、IO设备,开销远大于创建和撤销线程的开销,线程的切换只需要保存和设置少量寄存器的内容,开销小

  3. 如何选择进程和线程

    线程切换代价小,所以如果频繁创建、销毁却又包含大量计算,使用线程。例如界面显示和及时响应消息。

    允许并发且有阻塞的socket/磁盘通常也是用线程

  4. 线程间的通信方式

    临界区,使用互斥锁/信号量解决临界区变量

  5. 进程间的通信方式

    1. 管道

      1. 无名管道:半双工,只能用于父子进程间的通信,使用read/write、文件描述符进行读取,是内存中的特殊文件

        int fd[2] pipe(fd)创建无名管道,其中fd[0]为读端,fd[1]为写端

        <1>当写端存在时,管道中没有数据时,读取管道时将阻塞

        <2>当读端请求读取的数据大于管道中的数据时,此时读取管道中实际大小的数据

        <3>当读端请求读取的数据小于管道中的数据时,此时放回请求读取的大小数据

        <4>向管道中写入数据时,linux不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。当管道满时,读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。

        如果没有读端,写进程收到SIGPIPE信号

      2. 命名管道:FIFO半双工,是文件系统的特殊文件(存在于内核之中,无亲缘关系也可以使用其进行通信)

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJgLDY9S-1597977692345)(C:\Users\10232\AppData\Roaming\Typora\typora-user-images\image-20200812093709329.png)]

        image-20200812101134543

        特点:面向字节流,生命周期随内核,自带同步互斥机制,半双工、单向通信

    2. 消息队列:存放于内核之中,不一定按照顺序进行读取

      ​ 消息队列由标识符唯一标记

      1. 消息队列可以认为是一个全局的一个链表,链表节点钟存放着数据报的类型和内容,有消息队列的标识符进行标记。
      2. 消息队列允许一个或多个进程写入或者读取消息。
      3. 消息队列的生命周期随内核。
      4. 消息队列可实现双向通信。
    3. 信号量:进程间的互斥与同步

      同一进程中互斥,不同进程中同步

      在保证互斥的同时提高了并发,可用于进程、线程间的同步

      sem_init sem_wait sem_post

    4. 共享内存:最快的IPC方式

      但是要保证共享内存处的线程安全问题。(epoll使用mmap进行共享内存,减少了从内核中拷贝数据到进程段的时间)

      1. 不用从用户态到内核态的频繁切换和拷贝数据,直接从内存中读取就可以。
      2. 共享内存是临界资源,所以需要操作时必须要保证原子性。使用信号量或者互斥锁都可以。
      3. 生命周期随内核。
    5. socket

    6. 信号

    7. 文件(fork后使用相同的文件描述符)

  6. 线程同步与线程异步

    1. 线程同步:一个线程需要等待另一个线程执行结束后才能进行
    2. 线程异步:无需等待另一个线程结束
    3. 同步的目的:多线程操作数据保证前后一致性
    4. 互斥的目的:多线程之间进行数据操作不会相互影响
  7. 多线程同步、互斥的方法

    互斥锁、信号量,信号,条件变量

    互斥锁: pthread_mutex_init

    ​ pthread_mutex_destroy

    ​ pthread_mutex_lock

    ​ pthread_mutex_unlock

    信号量:阻塞sem_wait

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5MskuTxQ-1597977692347)(C:\Users\10232\AppData\Roaming\Typora\typora-user-images\image-20200811223759634.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uq5q567Y-1597977692350)(C:\Users\10232\AppData\Roaming\Typora\typora-user-images\image-20200806213446193.png)]

  8. 进程上限:4096,使用limit -u 进行修改

    最终上限:pid的上限32768(short)

    线程上限:依据虚拟内存大小/线程栈的大小决定(大约300个) 3072M/8M = 384个

    由于代码段和数据段-1个,主线程-1个为382个

    使用ulimit -s减少栈的大小,但是无法突破1024的上限

  9. 协程

    一个进程包含多个线程,一个线程包含多个协程

    协程又称微线程、纤程。在执行过程中,在子程序内部可中断。然后去执行别的子程序,再返回来继续执行

    协程的子程序之间可以随时中断去执行另一个协程,特点为在一个线程执行

    协程切换不消耗资源在用户态进行

    线程内的多个协程进行切换,但是是串行执行的

    协程为非抢占式调度,切换由程序员控制

  10. 进程、线程、协程的切换对比

    1. 进程的切换者–操作系统,用户无感知,使用进程的调度算法(FCFS,SJF,时间片…)

      切换内容:全局目录,内核栈,硬件上下文保存在内存中,nei内核态-用户态-内核态

      上下文切换补充:

      1. 切换页目录使用新的地址空间–虚拟空间变换
      2. 切换内核栈、硬件上下文
      3. 更换PCB
      4. 页表
      5. 刷新TLB(是页表的cache)
    2. 线程的切换者–操作系统,用户无感知

      切换内容:线程栈、硬件上下文。线程切换内容保存在内核栈中,nei内核态-用户态-内核态

    3. 协程的切换者–用户,只是切换硬件上下文,切换内存保存在用户自定义变量中,没有进入内核态

  11. 孤儿进程和僵尸进程

    1. 孤儿进程:父进程先于子进程结束,子进程由init收养,回收由init进行
    2. 僵尸进程:子进程退出,资源未被父进程回收,但是占用了进程号等资源。如果大量存在僵尸进程,那么可能会导致进程号分配不足而无法fork
    3. 解决方案
      1. kill父进程,让子进程转由init收养
      2. 子进程退出时向父进程发送SIGCHRD信号,信号处理函数中进行回收
  12. 进程调度算法:

    1. FCFS:每次从就绪队列中选取头部调度

    2. SJF:平均等待时间、平均周转时间最低

      对长作业不利,容易造成饥饿现象

      程序运行时间由用户提供,不一定真正做到短作业优先

    3. 优先级调度算法

    4. 时间片轮转算法

    5. 多级反馈队列调度算法:多个FCFS+时间片轮转

  13. 多进程与多线程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RFHUgzyU-1597977692353)(C:\Users\10232\AppData\Roaming\Typora\typora-user-images\image-20200812094158313.png)]

    1. 频繁的创建、销毁:线程 – web服务器
    2. 大量计算:线程 – 切换频繁
    3. 强相关:进程 – 消息解码、消息处理
    4. 弱相关:线程 – 消息接受、消息发送
    5. 多机分布:进程 多核分布:线程

    IO密集型:多线程

    CPU密集型:多进程

  14. 进程和线程的概念

    1. 进程是运行时对程序的封装,是操作系统资源调度和分配的基本单位,实现了操作系统的并发
    2. 线程是进程的子任务,是CPU调度的基本单位,实现进程的并发。线程独自占用虚拟存储器,拥有独立的寄存器组、PC和处理器状态,但是公用地址空间和文件队列
  15. 线程存在的必要性

    1. 资源节省 – 切换开销小 – 通信方便

    2. 进程存在缺点:同一时间只能干一件事,如果有阻塞整个进程会被挂起

      引入线程作为并发调度的基本单位,减少程序并发执行付出的时空开销

    3. 从资源上来讲,线程是一种非常"节俭"的多任务操作方式。在linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。

    4. 从切换效率上来讲,运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右

    5. 从通信机制上来讲,线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同

你可能感兴趣的:(后端,面试)