C++服务端面试准备(4)Linux及多线程相关

声明:本文内容纯属博主自己查找和归纳的个人所需的知识点,仅作参考,如有错误,博主强烈希望您指出。如果您是某个知识点的原创博主,如有需要,可联系本人加上链接。本文内容会根据博主所需进行更新,希望大家多多关照。

文件IO相关知识点

  • 七种文件类型:普通文件(-)、目录(d)、符号链接(l)、管道(p)、套接字(s)、字符设备(c)、块设备(b)
  • shell中文件的颜色:白色——普通文件、绿色——可执行文件、红色——压缩文件、蓝色——目录、青色——链接文件、黄色——块设备字符设备管道、灰色——其他文件
  • PCB:进程控制块,存放文件描述符表,本质是一个结构体
  • PCB内部成员:进程ID、进程状态、当前工作目录、文件描述符表、用户ID组ID、信号相关的信息等
  • 一个进程有一个文件描述符表,大小为1024字节,前三个被占用,分别是STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO
  • 文件描述符作用:寻找磁盘文件
  • 查看磁盘命令:df -h

并发与并行

  • 并发:

       是指同一个时间段内多个任务同时都在执行,并且都没有执行结束。并发任务强调在一个时间段内同时执行,而一个时间段由多个单位时间累积而成,所以说并发的多个任务在单位时间内不一定同时在执行 。

  • 并行:

       是说在单位时间内多个任务同时在执行。

       在多线程编程实践中,线程的个数往往多于CPU的个数,所以一般都称多线程并发编程而不是多线程并行编程。

进程相关知识点

  • 查看进程:ps auxps ajx能显示父进程,查看内存占用:free -h,查看当前cpu:top
  • MMU:虚拟内存映射单元,单位为4k,虚拟内存通过MMU映射到物理内存
  • 寄存器4字节,1字节8位
  • 进程基本状态:初始态、就绪态、运行态、挂起态、终止态

       运行态->挂起态:等待使用资源,如等待外设传输、人工干预等
       运行态->就绪态:出现有更高优先权进程

  • 父子进程共享:文件描述符、mmap映射区、全局变量读时共享、写时复制
  • 孤儿进程:父进程先于子进程结束,子进程称为孤儿进程,init进程变为它的父进程并接管孤儿进程
  • 僵尸进程:当子进程比父进程先结束,而父进程又没有回收子进程,子进程的进程描述符仍然保存在系统中,此时子进程将成为一个僵尸进程

进程间通信相关知识点

  • 进程间常用的4种通信方式:管道(使用简单)、信号(开销小)、共享映射区(无血缘关系可用)、本地套接字(稳定)
  • 管道:

       本质为一个伪文件,内核使用环形队列机制,借助内核缓冲区(4k)实现,数据一旦读走,管道中不在存在

  • 共享映射区:

       mmap,一种内存映射文件的方法,将一个文件或者其它对象映射进内存,无血缘关系的进程间可使用共享映射区进行通信,在要求高性能的应用中比较常用。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关

  • 信号:

       简单但不能携带大量信息,满足条件才发送,相当于中断,有很强的延时性,所有信号都由内核(PCB)发送和处理
       信号4要素:编号、名称、事件、默认处理动作
       信号处理方式:执行默认动作(终止进程、忽略信号、终止进程并生成CORE文件、暂停进程、继续运行进程)、忽略、捕捉

  • 查看信号列表:kill -l

创建会话要注意什么

  • 创建会话的进程不能是进程组组长,否则出错返回
  • 该进程变成新会话的首进程和一个新进程组组长
  • 需要有root权限(Ubuntu不需要)
  • 新会话丢弃原有的控制终端,该会话没有控制终端
  • 创建会话时,先调用fork,父进程终止,子进程再调用setsid()

创建守护进程

  1. 在父进程中执行fork并exit退出;
  2. 在子进程中调用setsid函数创建新的会话;
  3. 在子进程中调用chdir函数,让根目录 "/" 成为子进程的工作目录;
  4. 在子进程中调用umask函数,设置进程的umask为0;
  5. 在子进程中关闭任何不需要的文件描述符(close(fd))或者重定向"/dev/null"

线程相关知识点

  • 查看所有线程:top -Hps -Lf 进程号,查看该进程的线程
  • 线程定义:LWP,轻量级进程,有独立的PCB和共享地址空间
  • 为什么会出现线程安全:当一个线程在操作共享资源时,未执行完毕的情况下,其他线程参与进来,导致共享资源出现安全问题。
  • 保护共享数据区方法:互斥量、读写锁、条件变量、信号量(C语言)、原子操作(通常用来保护单个变量)、windows临界区
  • 死锁:对一个锁反复Lock,或者2个线程各自锁上一把锁后请求对方的锁
  • 死锁解决方法:按同样的顺序上锁,c++11用std::lock(mutex1, mutex2, ...)

条件变量的使用

  1. 创建一个互斥量,用lock_guard或者unique_lock封装互斥量
  2. 创建条件变量,用wait成员函数等待条件满足
  3. 另一线程使用notify_one或notify_all后激活wait
  4. wait拿锁后判断第二个参数是否为true,如果true则执行下面的代码,如果false则表示条件不满足,继续等待下次的激活

sleep()和wait()的区别

  • sleep()包含头文件,wait()包含文件
  • sleep()占着CPU休眠,不会解锁,wait()释放锁并腾出CPU资源等待唤醒并拿锁

进程与线程的区别

  1. 进程是分配资源的基本单位;线程是系统调度和分派的基本单位。
  2. 一个进程可以拥有多个线程,属于同一进程的线程,具有相同的地址空间,堆是共享的,栈是私有的。
  3. 进程开销大,线程开销小
  4. 线程间通信相对方便,进程间通信相对复杂
  5. 多进程更健壮,多线程容易出错
  6. 进程是一个资源的容器,为进程里的所有线程提供共享资源,是对程序的一种静态描述,线程是计算机最小的调度和运行单位,是对程序的一种动态描述

为什么使用多线程

  1. 避免阻塞

       单个线程中的程序,是顺序执行的。如果前面的操作发生了阻塞,那么就会影响到后面的操作。这时候可以采用多线程

  1. 避免CPU空转

       有时候处理一条请求,会涉及到数据库访问、磁盘IO等操作,这些操作的速度比CPU慢很多,而在等待这些响应的时候,CPU却不能去处理新的请求,没有充分利用资源,这时多线程就发挥作用了

  1. 提升性能

       在满足条件的前提下,多线程确实能提升性能:
       第1,任务具有并发性,子任务之间不能有先后顺序的依赖,必须是允许并行的,另外,还不能有资源竞争
       第2,只有在CPU是性能瓶颈的情况下,多线程才能实现提升性能的目的。
       第3,就是需要有多核CPU才行,如果上述条件都满足,有一个经验公式可以计算性能提升的比例,叫阿姆达尔定律:
       速度提升比例 = 1/[(1-P)+(P/N)]
       其中P是可并行任务的比例,N是CPU核心数量

gdb基础调试

编译:gcc加要加-g,-g保留函数名和变量名
启动:gdb 可执行文件
传参:set args 参数1 参数2 ...

操作 命令
查看第n行的上下程序 l n
设置查看n行上下程序 set listsize n
查看其他文件程序 l 文件名 : n 或 函数名
在n行打断点 b n
查看断点 i b
删除断点 d n,n为断点编号
设置无效或有效断点 dis / enb n,n为断点编号
条件断点 b n if 变量==值
运行 r 或 start,r直接到断点,start进入程序第一行
单步执行 n
执行到下一个断点 c
查看变量信息 p 变量名
查看变量类型 ptype 变量名
一直显示变量信息 display 变量名
取消显示变量 先i display 查看变量编号n,然后undisplay n
进入函数 s
离开函数 finish
跳出循环 until,需要删除断点
离开gdb q

gdb也可以直接调试core文件查看错误:

1.当一个程序出现段错误时,会出现以下提示:
       Segmentation fault (core dumped)
       core 指该程序运行时,进程空间的内存分布
       dumped 表示内核已经把core抛出
       通常,出现段错误提示时程序运行目录下应该自动生成一个core文件用来存储内核抛出的core,但是,由于linux环境一般默认设置core文件限制为0,所以一般情况下无法生成core文件。

2.查看core文件大小限制:ulimit -c

3.将其修改为无限制:ulimit -c unlimited

4.运行命令:gdb 执行文件名 core

5.gdb输入  where

gdb多线程调试

命令行查看线程信息:

  • 查看当前运行的进程:ps aux|grep 执行文件名
  • 查看当前运行的轻量级进程:ps -aL|grep 执行文件名
  • 查看主线程和新线程的关系:pstree -p 主线程id

线程栈结构的查看:

  • 获取线程ID
  • 通过命令查看栈结构:ps stack 线程ID

利用gdb查看线程信息:

  • 将运行着的进程附加到gdb调试器当中,查看是否创建了新线程:gdb attach 主线程ID

进入gdb调试:

  • info inferiors:查看当前进程
  • info threads:查看当前线程
  • bt:查看当前线程栈结构
  • thread n/ID:切换线程(n代表第几个线程)
  • break 行号/函数名:设置当前线程断点
  • break file.c:100 thread all:在file.c文件第100行处为所有经过这里的线程设置断点。
  • set scheduler-locking off/on/step
    在使用step或者continue命令调试当前被调试线程的时候:
    off:不锁定任何线程,也就是所有线程都执行,这是默认值。
    on:只有当前被调试程序会执行。
    step:在单步的时候,除了next过一个函数的情况以外(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为),只有当前线程会执行。
  • thread apply ID1 ID2 command:让一个或者多个线程执行GDB命令command
  • thread apply all command:让所有被调试线程执行GDB命令command

makefile基础

  • 向下检索内容,根据时间判断哪个文件需要更新
  • 目标:依赖
           命令
  • 查找目录下指定文件类型:变量 = $(wildcard 文件路径)
  • 匹配替换:变量 = $(patsubst 原名称, 替换后的名称, 源文件)
  • notdir:去除文件名的目录函数
  • basename:取文件名函数
  • $@:规则中的目标 $<:规则中的第一个依赖 $^:规则中所有依赖
  • /:换行
  • 自定义命令:
    名称:
           命令
  • 声明伪目标:
    .PHONY:名称

随便说一下Linux和Windows的区别

  1. 兼容性:windows向后兼容,linux内核升级可能就不兼容,软件方面windows占优
  2. 进程:单个进程windows不能执行,linux可以
  3. 性能:windows图形界面,很多渲染,性能降低,linux少,用命令行工作,性能好
  4. 文件系统:linux有多种文件系统,对文件和设备的管理占优,windows较差
  5. 中断优先级:windows有32级,linux似乎只有5级,异常分为故障(fault)、陷阱(trap)和中止(abort)
  6. 软件启动:windows用注册表,linux用配置文件,灵活性高

你可能感兴趣的:(后端,c++,c,golang)