0%

进程管理

进程、线程、协程的区别

本质区别:进程是操作系统资源分配的基本单位,线程是是任务调度和执行的基本单位,协程是用户态轻量级的执行单元

  • 进程:进程是操作系统资源分配和调度的基本单位,有独立的内存空间和系统资源。每个进程有自己独立的堆栈,进程间通信需要通过管道、消息队列、信号量等机制。由于进程有独立的内存空间,因此有较高的稳定性与安全性,上下文切换要保存和恢复整个进程状态,因此开销大。
  • 线程:线程是CPU调度和执行的基本单位。线程共享进程的内存空间,包括堆和全局变量(每个线程有自己的栈)。线程上下文切换因为只需要保存自己的上下文而不是整个进程的,因此开销较小。由于多个线程共享内存空间,因此存在数据竞争和线程安全问题,需要通过同步和互斥机制来解决。
  • 协程:协程是用户态的轻量级执行单元,由程序自身调度,而非操作系统内核。它基于线程实现,多个协程共享所属线程的资源,但每个协程有自己的栈和程序计数器。协程上下文切换完全由用户代码控制,无需内核参与,开销极小。协程适合处理大量 IO 密集型任务,通过 “协作式调度” 避免线程阻塞,在单线程中实现高并发;但在 CPU 密集型任务中优势不明显,且需要开发者手动处理协程间的同步问题。

为什么进程崩溃不会对其他进程产生很大影响

  • 进程的隔离性:每个进程有自己独立的内存空间,进程崩溃,内存被操作系统回收,不影响其他进程的内存空间。进程崩溃不会影响其他进程执行。
  • 进程的独立性:每个进程独立运行,不会共享资源。进程崩溃不会对其他资源产生影响。

线程崩溃会破坏进程的共享内存空间和资源状态,操作系统为了避免错误扩散,会回收进程资源,这会将整个进程和所有线程一起回收

进程分配的资源是什么

虚拟内存、文件句柄、信号量等

有了进程,为什么还需要线程

  • 降低切换开销:线程共享进程资源,创建、销毁和切换速度远快于进程,大幅减少系统消耗。
  • 便于数据共享:同一进程内线程可直接访问共享内存,无需复杂的进程间通信,数据交互更高效。
  • 利于并发:线程是 CPU 调度的最小单位,多线程能充分利用多核 CPU,且单进程内可创建大量线程,实现更细粒度的并发控制。

多线程的优势与劣势

  • 优势:发挥利用多核处理器的资源,同时处理多个任务,提高程序运行效率
  • 劣势:存在多线程数据访问竞争问题,需要通过锁机制保证线程安全,增加了锁的开销,还会有死锁风险。多线程消耗更多的资源,如内存和CPU,每个线程都需要占用一定的内存和处理时间。

为什么多线程不是越多越好

  • 切换开销:线程的创建与切换需要消耗系统资源,包括内存与CPU。如果创建太多线程会占用大量系统资源,导致系统负载过高,进而导致线程崩溃
  • 死锁风险:过多的线程可能会导致竞争条件和死锁

进程切换和线程切换的区别

进程切换:包括整个进程的地址空间,全局变量,文件描述符,内存映射表等,开销大
线程切换:涉及线程的堆栈,寄存器,程序计数器等,开销小

线程切换的详细过程

  1. 上下文保存:上下文信息包括寄存器状态,程序计数器,堆栈指针等,用于保存线程的执行状态
  2. 切换到调度器:将执行权切换到调度器。调度器负责选择下一个要执行的进程,并根据调度算法进行决策
  3. 上下文恢复:调度器选择下一个要执行的线程后,会从改线程保存的线程上下文恢复线程的执行状态
  4. 切换线程:调度器将执行权切换到新线程,使其开始执行

上下文信息一般由操作系统负责保存,一般情况下保存在线程控制块(TCB)中

TCB:操作系统用于管理线程的数据结构,包含线程状态,寄存器的值,堆栈信息等。当发生线程切换时,操作系统会切换TCB来保存和恢复线程的上下文

进程状态的切换过程

进程管理-2025-10-22-00-31-26

  • null->新建状态 一个新进程被创建时的状态
  • 新建状态->就绪状态:线程新建完成并完成初始化
  • 就绪状态->运行状态:就绪状态被操作系统调度器选中后分配给CPU运行进程
  • 运行状态->终止状态:进行运行完成或出错
  • 运行状态->就绪状态:分配的时间片用完变为就绪态,操作系统选择其他就绪态进程进入运行态
  • 运行状态->阻塞状态:当进程请求某个事件且必须等待时(如I/O请求)
  • 阻塞状态->就绪状态:当进程要等待的时间完成时

进程的上下文有哪些

CPU上下文:寄存器,程序计数器等CPU运行前必须依赖的环境
CPU上下文切换:把前一个任务的CPU保存起来(系统内核保存),然后加载新任务的CPU上下文,跳转到程序计数器的新位置执行任务

进程是由内核进行调度和管理的,进程调度发生在内核态

进程的上下文切换包括虚拟内存、栈、全局变量等用户空间资源,还包括内核堆栈、寄存器等内核空间的资源

通常把交换信息保存在进程的PCB中,当运行另一个进程时,从进程的PCB中取出上下文,然后恢复到CPU,使进程可以继续执行

进程间的通讯方式有哪些

管道

管道分为匿名管道和命名管道

  • 匿名管道:匿名管道是特殊的文件,只存在于内存,不存在于文件系统,|就是匿名管道,通讯的数据是无格式的流并且大小受限,通信方式是单向的,数据只能在一个方向上流动;匿名管道只能用于存在亲缘关系(父子/兄弟)的进程间通信,通信管道的生命周期随线程的建立而建立,随线程的终止而消失
  • 命名管道:命名管道需要在文件系统创建类型为p的设备文件,那么毫无关系的进程就能通过设备文件通信。

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据也是从内核中读取,通信数据先进先出,不支持lseek之类的文件操作。

消息队列

克服了管道通信的数据是无格式的字节流的缺点,消息队列实际上是保存在内核的消息链表,消息队列的消息体可以由用户自定义类型,发送数据被分成一个个独立的消息体,接收方要保证和发送方数据类型一致。消息队列通信速度不是最及时的,因为每次数据的写入和读取都需要经过用户态和内核态之间的拷贝过程

共享内存

共享内存可以用来解决消息队列通信中用户态与内核态之间数据拷贝带来的开销,他直接分配一个共享空间,每个进程都可以直接访问,就像访问自己进程的空间一样便捷,不需要陷入内核态和系统调用,大大提升了通信速度。但是多进程竞争同一共享内存会导致错乱,需要信号量来保护共享资源

信号量是操作系统中用于解决进程(或线程)间同步与互斥问题的核心机制,本质上是一个计数器,通过对计数器的原子操作(P/V 操作)来控制多个进程对共享资源的访问。

信号

信号是操作系统向进程发送的异步事件通知,用于告知进程发生了特定事件,迫使进程暂停当前操作并响应事件。进程有三种方式响应信号:1.执行默认操作,2.捕捉信号,3.忽略信号。其中SIGKILLSIGSTOP无法被捕捉或忽略,方便我们能在任何时候结束或停止某个进程

Socket

Socke用于不同主机的的进程间通信,也可以用于本地主机的进程间通信
可以根据创建的Socket类型,分为三种常见的通信方式:

  • 基于TCP协议的通信方式
  • 基于UDP协议的通信方式
  • 本地进程间通信

信号和信号量的区别

  • 信号:处理异步事件的方式,信号是一种比较复杂的通信方式,用于通知接收进程有某种事件发生,除了用于进程外,还可以发信号给进程本身
  • 信号量:进程间通信处理同步互斥的机制,本质上是一个计数器,通过对计数器的原子操作(P/V 操作)来控制多个进程对共享资源的访问

共享内存怎么实现

共享内存机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存

线程间的通讯方式

互斥锁

互斥锁本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量加锁以后,任何其他试图再次对互斥量加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的线程都会变成可运行状态,第一个变为可运行状态的线程对互斥锁加锁,其他线程被互斥锁锁住,只能回去再次等待变为可用

条件变量

条件变量时利用线程共享的全局变量进行同步的一种机制,主要包括两个动作:

  1. 一个线程等待“条件变量的条件成立”而挂起
  2. 另一个线程使条件成立

为了防止竞争,条件变量的使用总是和一个互斥锁结合起来。线程在改变条件状态时必须锁住互斥量,函数pthread_cond_wait把自己放在等待条件的线程列表上,然后对互斥锁解锁。函数返回时,互斥锁再次锁住。

自旋锁

特点:当线程尝试获取锁但是锁已经被占用时,不会立即阻塞,而是通过循环重试(忙等)的方式来持续检查锁的状态,直到获取锁为止。

自旋锁通过CPU提供的CAS函数,在用户态完成加锁和解锁操作,不会主动产生线程上下文切换,因此开销更小,自旋锁会占用CPU资源,因此适合锁被占用时间极短的场景

一般加锁的过程:

  1. 查看锁的状态,如果锁是空闲的,执行第二步
  2. 将锁设置为当前线程所有

CAS合并这两步形成原子指令

注意:单核CPU需要抢占式调度器,否则自旋锁不能使用,因为自旋锁不会放弃CPU的控制权

信号量

通常信号量表示资源的数量,对应的变量是一个整形(sem)变量。另外还有两个原子操作的系统调用函数来控制信号量:P/V操作

P操作:

  • 将sem减去1
  • 如果此时sem<0,则线程/进程进入阻塞状态,否则继续

V操作:

  • 将seg加上1
  • 如果此时sem<=0,则进入等待队列唤醒一个进程,否则继续执行

读写锁

读写锁分为[读锁]和[写锁],如果只读取共享资源用读锁加锁,如果要修改贡献资源用写锁加锁。因此读写锁适用于能明确区分读写操作的场景。

读写锁的工作原理:

  • 当写锁没有被持有时,多个线程能并发地持有写锁,大大提高了共享资源的访问速率,因为读锁时用于读取共享资源的场景,所以多个线程同时持有读锁也不会破坏共享资源的数据
  • 一旦写锁被线程持有,读线程获取读锁的操作会被阻塞,而其他写线程获取写锁的操作也会被阻塞。所以写锁是独占锁,因为任何时刻只有一个线程能持有写锁

读写锁在读多写少的场景能发挥优势

进程调度算法有哪些

先来先服务(FCFS)

每次从就绪队列选择先进入队列的进程,然后一致运行,直到进程退出或被阻塞,才会继续从对列出选择第一个进程接着运行

FCFS对长队列有利,但对短队列不利,适用于CPU密集型任务,不使用于I/O密集型任务

最短作业优先(SJF)

优先选择运行时间最短的进程来运行,有助于提高系统吞吐量

这对长队列不利

高响应比优先调度算法(HRRN)

每次进行线程调度前,先计算响应比优先级,然后吧响应比优先级最高的进程投入运行

响应比优先级计算公式:
$$\text{优先权} = \frac{\text{等待时间} + \text{要求服务时间}}{\text{要求服务时间}}$$

权衡了短作业和长作业

时间片轮转调度算法(RR)

最古老、最公平、最简单、使用最广的算法

每个进程被分配一段时间段,称为时间片,允许该进程在该时间段内运行

  • 如果时间片用完,进程还在运行,那么会把进程从CPU中释放出来,并把CPU分配给另一个进程
  • 如果该进程在时间片结束前阻塞或结束,CPU立即结束

另外需要注意:

  • 如果把时间片设置的过短会导致过多的上下文切换,降低CPU效率
  • 如果设的太长又可能引起短作业进程响应时间变长

通常时间片设在20ms~50ms是比较合理的折中值

最高优先级调度算法(HPF)

HPF的调度是有优先级的,即希望从就绪的队列中选择优先级最高的进程进行运行
进程的优先级分为静态优先级和动态优先级:

  • 静态优先级:创建进程时,就已经确定优先级了,然后整个运行时间优先级都不会变化
  • 动态优先级:根据进程的动态变化调整优先级,如随等待时间增加进程的优先级

该算法也有两种处理优先级高的方式:

  • 非抢占式:当就绪队列出现优先级高的进程时,运行完当前进程,再选择优先级高的进程
  • 抢占式:当就绪队列出现优先级高的进程时,当前进程挂起,优先级高的进程先执行

多级反馈队列调度算法(MFQ)

是RR和HPF的综合版

  • 多级:多个队列,每个队列优先级由高到低,同时优先级越高时间片越短
  • 反馈:如果有新的进程加入优先级高的队列时,立即停止当前运行的进程,转而取运行优先级高的进程

工作过程:

  • 设置多个队列,每个队列优先级由高到低,同时优先级越高时间片越短
  • 新的队列会被放入最高级队列的末尾,按先来先服务排队等待调度,如果最高级队列分配的时间片没运行完成,将其转入下一级队列末尾,以此类推,直至完成
  • 当较高优先级队列为空,才调度较低优先级队列进程运行。如果运行时有新进程进入较高优先级队列,则停止当前运行的进程将其移动到队列的末尾,接着让较高优先级的队列先运行

兼顾了长短作业,同时有较好的响应时间