进程

进程线程&区别

  • 进程:程序一次执行过程,是系统分配资源(虚拟内存、文件句柄、信号量等)的基本单位

  • 线程:CPU调度和执行的基本单位

  • 区别:进程资源独立、创建切换开销大、通信复杂。线程资源共享、开销较小、通信简单。

引申问题:进程、线程、协程区别

  1. 进程

    1. 操作系统进行资源分配和调度的基本单位,拥有独立内存空间和系统资源、拥有独立的堆和栈。

    2. 进程间通信需要通过特定机制(管道、消息队列等)

    3. 稳定性和安全性高,上下文切换开销大(需要保存整个进程状态)

  2. 线程

    1. 进程内部执行单元,也是CPU调度和分派的基本单位。线程间共享进程内存空间,包括堆和全局变量

    2. 线程间通信更加高效,可以直接读写共享内存。

    3. 线程上下文切换开销较小,保存和恢复线程上下文即可;由于多个线程间共享内存空间,因此存在数据竞争和安全问题(需要同步和互斥机制解决)。

  3. 协程

    1. 用户态的轻量级线程,调度完全通过用户程序控制,不需要内核参与。线程拥有自己的寄存器、上下文和栈,与其他协程共享堆内存。

    2. 协程切换开销非常小,只需要保存和恢复协程上下文,无需进行内核级上下文切换。

    3. 协程需要程序员显式地进行调度和管理,相对于线程和进程,编程模型更加复杂

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

  • 进程隔离性:进程拥有自己独立的内存空间,当一个进程崩溃时,内存空间会被操作系统回收,不会影响其他进程的内存空间。

  • 进程独立性:每个进程独立运行,之间不共享资源,一个进程崩溃通常不会对其他进程资源产生影响

多线程优缺点(相比单线程)

  • 优势:提高程序运行效率,充分利用多核CPU资源,加快程序运行速度

  • 劣势:多线程的数据竞争访问问题,需要保证线程安全(通过锁等机制),增加锁开销,还存在死锁风险。还会占用更多系统资源(CPU、内存)

线程切换为什么比进程切换更快,节省了什么资源

线程切换比进程切换快是因为线程共享类同一进程的地址空间和资源,线程切换只需要切换栈和程序计数器、寄存器等,不涉及进程级别资源(虚拟内存、堆、文件句柄等)。

线程切换过程

  1. 上下文保存:操作系统决定切换到另外一个线程时,首先保存当前线程的上下文信息。包括寄存器状态、程序计数器、堆栈指针等。

  2. 切换到调度器:操作系统将执行权切换到调度器,调度器负责选择下一个要执行的线程,并根据调度算法决策

  3. 上下文恢复:从选择的下一个线程保存的上下文中恢复线程执行状态

  4. 切换到新线程:调度器将执行权切换到新线程,使其开始执行

上下文信息保存通常由操作系统负责管理,具体位置取决于操作系统实现。一般保存在线程控制块Thread Control Block中。

为什么进程之下需要设计线程

  1. 轻量级并发,充分利用多核CPU资源:进程创建/销毁开销大,数量受限;线程是更加轻量的调度实体,能够轻松创建成百上千并发任务,充分利用CPU资源

  2. 高效数据共享&协作:进程间地址空间隔离,通信(管道、消息队列……)需要通过内核拷贝,效率低下;线程间天然共享内存地址空间(堆、全局变量),数据传递无需拷贝,配合同步机制非常适合高并发协作场景

  3. 线程切换开销低:线程切换进切换线程私有资源(栈、程序计数器、寄存器……),无需切换页表,避免进程切换的巨大开销

引申问题:为什么不干脆只使用线程,完全舍弃进程?

进程是资源分配的基本单位,提供强大的隔离性(一个进程崩溃不会影响其他进程)。线程是CPU执行2-的基本单位,能够提升性能。

进程状态

基本状态:

  1. 创建

  2. 就绪:具备运行条件,等待CPU

  3. 运行:占用CPU运行

  4. 阻塞:等待事件(例如IO)

  5. 终止

转换关系:

  1. 就绪→运行:调度程序分配CPU

  2. 运行→就绪:时间片用完/被抢占

  3. 运行→阻塞:等待事件(IO请求等)

  4. 阻塞→完成:事件完成(IO结束)

进程上下文切换

进程由内核管理&调度,进程切换只能发生在内核态。

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

进程通信方式

  1. 管道

  2. 消息队列

  3. 共享内存

  4. 信号

  5. 信号量

  6. Socket

管道

  1. 匿名管道:缓存于内存的特殊文件;没有名字标识。通信数据为无格式流并且大小受限,为单向通信。匿名管道只能用于存在父子关系的进程间通信,生命周期随着进程创建而建立,随着进程终止而消失

  2. 命名管道:;亲缘关系进程间通信;需要在文件系统创建一个设备文件,通过设备文件通信。

无论匿名管道还是命名管道,进程写入数据都是缓存在内核中,通信符合先进先出原则。

消息队列

克服管道数据是无格式字节流的问题;

实际上保存在内核的消息链表中,消息队列消息体可以是用户自定义数据类型。发送数据时会被分为一个个独立的消息体;接收数据时也要与发送方发送的消息体数据类型保持一致。

共享内存

解决消息队列通信用户态和内核态间数据拷贝带来的开销。直接分配一个共享空间,每个进程都可以访问,提升了通信速度。

但是多个进程竞争共享资源可能造成数据错乱

信号量

通过信号量保护共享资源,任何时刻只有一个进程访问共享资源(互斥访问)

同时信号量还可以实现进程间同步(通过检查信号量状态是否满足操作条件)

信号

信号是异步通信机制,可以在应用进程和内核之间交互,可以利用信号通知用户空间的进程发生了哪些系统事件。

信号事件来源主要有硬件来源(键盘ctrl+c)和软件来源(kill命令)。

进程有三种方式响应信号:

  1. 执行默认操作

  2. 捕捉信号

  3. 忽略信号

Socket

不同主机进程间通信通过Socket执行。可以用于不同主机进程间通信以及本地主机进程间通信。

通信方式:

  1. 基于TCP

  2. 基于UDP

  3. 基于本地进程间通信

共享内存实现

拿出一块虚拟地址空间,映射到相同的物理内存中

线程间通信方式

  1. 互斥锁(mutex):访问共享资源前对互斥量加锁,访问完成后释放互斥量上的锁。

  2. 条件变量:利用线程间共享的全局变量进行同步;线程等待条件变量条件成立而挂起,另外线程使条件成立(条件变量使用总和互斥锁相结合)

  3. 自旋锁:通过CPU提供的CAS函数在用户态完成加锁&解锁操作;发生多线程竞争情况,加锁失败线程会忙等待直到拿到锁(可以使用while循环实现)。

  4. 信号量:用于控制对资源的访问次数

  5. 读写锁:由读锁和写锁两部分构成

常见进程调度算法

  1. 先来先服务

  2. 短作业优先:优先调度估计运行时间最短的进程

  3. 优先级调度:为每个进程分配优先级,优先调用高优先级

  4. 时间片轮转:CPU事件划分为固定时间片,按照就绪队列顺序轮流分配

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐