四大功能
- 线程是cpu调度的基本单位、进程是资源分配的基本单位
- 进程有自己独立的地址空间、线程共享所属进程的地址空间
- 进程是拥有系统资源的一个独立单位,而线程基本不拥有系统资源,只拥有一点在运行时必不可少的资源(如程序计数器,一组寄存器和栈),和其他线程共享本进程的相关资源(如内存、I/O、cpu等) 不同进程之间的资源是独立的
- 线程依赖于进程而存在、一个进程至少有一个线程
- 进程创建、撤销、切换的开销远大于线程的开销 创建或撤销进程时,系统都要为之分配或回收系统资源,如内存空间,I/O设备等 线程见共享虚拟地址空间,无需复制内存,复制页表 进程需要复制内存页表、文件描述符表之类的多种属性
- 线程通信更方便(同一进程下的线程共享全局变量等数据,而进程之间的通信需要以进程间通信(IPC)的方式进行)
- 多进程更加健壮(多线程只要一个线程奔溃,整个程序就奔溃了,但多进程程序中一个进程奔溃并不会对其他进程造成影响,因为进程有自己独立的地址空间)
- 进程适用于多核、多机分布;线程适用于多核
进程会重新拷贝地址空间,线程会划分地址空间(内核区中的栈、代码段等)线程共享内核区(进程id、父进程id、进程组id、会话id、用户id、用户组id、信号处置、文件描述符表、文件系统相关信息:文件权限掩码、当前工作目录)除栈、.text的虚拟地址空间, 线程独占线程id、信号掩码、线程特有数据、error变量、实时调度策略和优先级、栈、本地变量、函数的调用链接信息
死锁(deadlock)
两个或者多个并发进程中,因争夺资源而互相等待的现象,若无外力作用,它们都将无法推进下去,每个进程持有某种资源而又等待其它进程释放它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁
场景
- 忘记释放锁
- 重复加锁
-
条件
互斥资源
- 请求与保持(占有并等待)
- 非抢占式调度
-
处理方法
鸵鸟策略: 忽略它
- 因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任何措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略
- 允许某些资源同时被多个进程访问。但是有些资源本身并不具有这种属性,因此这种方案实用性有限
- 资源一次性分配,当一个进程开始运行之前,必须一次性向系统申请它所需要的全部资源,否则不运行
- 允许进程强行抢占被其它进程占有的资源。会降低系统性能
- 对所有资源统一编号,所有进程对资源的请求必须按照序号递增的顺序提出,即只有占有了编号较小的资源才能申请编号较大的资源。这样避免了占有大号资源的进程去申请小号资源
- 让某些进程回退到足以解除死锁的地步,进程回退时自愿释放资源。要求系统保持进程的历史信息,设置还原点
-
进程通信
管道(Pipe)
- 半双工,可以看作一种特殊的文件
- 其实是一个在内核内存中的缓冲区,大小有限(与操作系统相关)
- 拥有文件的性质,可以按照操作文件的方式对管道进行操作,匿名管道没有文件实体,有名管道有,但不存储数据
- 一个管道是一个字节流,管道不存在消息或消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小
- 管道中的数据是顺序的,读数据和写数据的顺序是一样的
- 分为读端和写段
- 数据结构是一个循环队列
- 有名管道(命名管道):允许无亲缘关系进程间的通信,在文件系统中作为一个特殊文件存在,当使用fifo的进程退出后,fifo文件将继续保存在文件系统中以便后续使用
- 无名(匿名)管道:没有名字,只能用于具有亲缘关系的进程之间通信
- 消息队列
- 消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取
- 信号量(Semaphore)
- 是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步
- 信号(Signal)
- 是事件发生时对进程的通知机制,有时也称为软件中断(是软件层次上的对中断机制的模拟,是一种异步通信方式)
- 通常是源于内核,
- 对于前台进程可以通过输入特殊的终端字符来给他发送信号,比如输入ctrl + c通常会给进程发送一个中断信号
- 硬件异常,即硬件检测到一个错误条件,通知内核,随即再由内核发送相应信号给相关进程。
- 系统状态变化
- 运行kill命令或者函数
- 用于通知进程某个事件的发生
- 简单
- 不能携带大量信息
- 满足某个条件才能发送
- 优先级比较高
- 共享内存
- 允许多个进程共享同一块物理内存
- 由于一个共享内存段会成为一个进程用户空间的一部分,减少了内核的介入,所要做的就是让一个进程将数据复制进共享内存中
- 没有缓冲区(相比管道等)
- 效率高
- Socket套接字通信(不同主机)
- 用于不同主机进程的通信
内存映射
互斥量Mutex (互斥锁)
- 采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问,保证同时仅有一个线程访问某项共享资源,可以使用互斥量来保证对任意共享资源的原子访问
- 只有持有互斥量的线程才能进入临界区
- 有两种状态,已锁定和未锁定,如何时候只有一个线程可以锁定该互斥量,只有持有互斥量的线程才能解锁
- 信号量Semaphore
- 计数器,允许多个线程同时访问同一个资源
- 条件变量
- 通过条件变量通知操作的方式来保持多线程同步
读写锁
先来先服务
- 短作业优先
- 高优先级优先
- 时间片轮转
- 最短剩余时间优先
-
孤儿进程
父进程结束后,子进程还没结束,这样的子进程称为孤儿进程
- 每有一个孤儿进程,内核就把进程的父进程设置为(init),而init进程会循环地wait()它的已退出的子进程
-
僵尸(Zombie)进程
子进程终止时,父进程未被回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程
- 不能被kill -9杀死
这会导致,如果父进程一直不释放子进程资源的话,那么进程号会被占用,但是系统的进程号是有限的,如果有大量僵尸进程,将因为没有可用的进程号,导致系统不能产生新的进程
守护进程(Daemon进程、精灵进程)
是Linux中的后台服务进程
- 生存期较长,会在系统启动时创建并一直运行到系统被关闭
- 通常独立于控制终端并且周期性的执行某种任务或等待处理某些事情的发生的事件,一般采用d结尾的名字
- 它在后台运行且不拥有控制终端。没有控制终端确保了内核永远不会为守护进程自动生成任何控制信号以及终端相关的信号(如SIGINT、SIGQUIT)
- Linux大多数服务器就是用守护进程实现的,比如Internet服务器inetd,Web服务器httpd
线程池
- 线程池是一种线程使用模式
- 线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价
- 线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量
- 线程池的应用场景:
- 需要大量的线程来完成任务,且完成任务的时间比较短
示例:WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的,因为单个任务小,而任务数量巨大;但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了,因为Telnet会话时间比线程的创建时间大多了
- 对性能要求苛刻的应用,但不至于使服务器因此产生大量线程的应用
示例:要求服务器迅速响应客户请求接受突发性的大量请求,突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误
协程
协程不是系统级线程,很多时候协程被称为“轻量级线程”、“微线程”、“纤程(fiber)”等。简单来说可以认为协程是线程里不同的函数,这些函数之间可以相互快速切换
协程和用户态线程非常接近,用户态线程之间的切换不需要陷入内核,但部分操作系统中用户态线程的切换需要内核态线程的辅助
协程是编程语言(或者 lib)提供的特性(协程之间的切换方式与过程可以由编程人员确定),是用户态操作。协程适用于 IO 密集型的任务。常见提供原生协程支持的语言有:c++20、golang、python 等,其他语言以库的形式提供协程功能,比如 C++20 之前腾讯的 fiber 和 libco 等等
如果业务处理时间远小于 IO 耗时,线程切换非常频繁,那么使用协程是不错的选择
协程的优势并不仅仅是减少线程之间切换,从编程的角度来看,协程的引入简化了异步编程。协程为一些异步编程提供了无锁的解决方案
协程 vs 线程
- 调度方式协程由编程者控制,协程之间可以有优先级;线程由系统控制,一般没有优先级
- 调度速度协程几乎比线程快一个数量级。协程调用由编码者控制,可以减少无效的调度
- 资源占用协程可以控制内存占用量,灵活性更好;线程由系统控制
- 创建数量协程的使用更灵活(有优先级控制、资源使用可控),调度速度更快,相比于线程而言调度损耗更小,所以真实可创建且有效的协程数量可以比线程多很多,这是使用协程实现异步编程的重要基础。同样因为调度与资源的限制,有效协程的数量也是有上限的
内存管理
最差适应算法,也称最差适配算法,它从全部空闲区中找出能满足作业要求的、且大小最大的空闲分区,从而使链表中的结点大小趋于均匀,适用于请求分配的内存大小范围较窄的系统。
