进程和线程区别

  • 进程(Process)

    • 操作系统资源分配的基本单位
    • 每个进程有自己独立的地址空间(代码段、数据段、堆、栈)
    • 程序一旦运行,就会至少有一个进程
    • 切换开销大,因为需要切换整个内存地址空间、文件描述符等上下文
  • 线程(Thread)

    • 程序执行的最小单位,是 CPU 调度的基本单位
    • 一个进程可以包含多个线程
    • 线程共享进程的地址空间,但有自己独立的栈和寄存器
    • 切换开销小,多个线程共享进程资源,只需切换少量上下文(寄存器、栈指针)

👉 直观类比:

  • 进程 = 公司(独立运作,有资源)
  • 线程 = 公司里的员工(共享公司资源,但干不同的活)

通信方面:

  • 进程间通信(IPC):麻烦,需要操作系统提供机制(管道、消息队列、共享内存、socket)
  • 线程间通信:方便,因为共享进程地址空间,直接读写共享变量即可,但需要同步机制(锁、条件变量)避免数据冲突

崩溃影响:

  • 进程:一个进程崩溃不会影响其他进程(只要没有依赖关系)
  • 线程:一个线程崩溃可能导致整个进程崩溃(因为共享地址空间)
对比点 进程 线程
定义 资源分配的基本单位 CPU 调度的基本单位
地址空间 独立 共享(代码、堆),但有独立栈
切换开销
通信 需要 IPC 直接共享内存,需同步
崩溃影响 不影响其他进程 可能拖垮整个进程
粒度
并发能力 多进程并发 多线程并发,更轻量

什么是线程同步,线程同步有哪些方法?

1. 什么是线程同步?

  • 线程同步 = 保证多个线程在访问共享资源时,按照一定的顺序执行,避免数据冲突。
  • 背景:多线程可以同时访问同一块内存,如果不加限制就可能出现“竞态条件”(race condition)。
  • 同步的目标:

    1. 保证数据一致性(不会读到脏数据)。

    2. 避免死锁和资源浪费。

    3. 提高并发效率。

👉 举例:
银行账户余额 = 1000 元。

  • 线程 A:取 800 元。
  • 线程 B:取 500 元。
    如果没有同步机制,可能两个线程都判断余额够 → 都取成功 → 最终余额变负数 ❌。
    同步机制可以保证操作的原子性:要么 A 成功 B 失败,要么 B 成功 A 失败。

2. 线程同步的方法

(1) 互斥锁(Mutex)

  • 最常见的方法。

  • 线程进入临界区前先加锁,用完资源后释放锁。

  • 保证同一时间只有一个线程能访问共享资源。

  • 缺点:可能导致阻塞,若使用不当会出现死锁。

C++ std::mutex,Java synchronized / ReentrantLock

(2) 读写锁(Read-Write Lock)

  • 区分 读锁写锁

    • 多个线程可以同时读(共享锁)。

    • 写操作必须独占(互斥锁)。

  • 适合读多写少的场景(如缓存、配置查询)。

Java ReentrantReadWriteLock,C++ std::shared_mutex

(3) 信号量(Semaphore)

  • 维护一个计数器,表示可用资源数量。

  • P 操作(等待):请求一个资源,计数器减一;如果不足就阻塞。

  • V 操作(释放):释放资源,计数器加一。

  • 常用于限流,比如线程池控制最大并发数。

Java Semaphore,Linux sem_t

(4) 条件变量(Condition Variable)

  • 与互斥锁配合使用。

  • 线程可以在条件变量上等待,直到某个条件为真再继续执行。

  • 常用于生产者-消费者模型

    • 消费者在队列空时等待条件变量;

    • 生产者放入数据后通知条件变量。

Java wait()/notify(),C++ std::condition_variable

(5) 自旋锁(Spinlock)

  • 当资源被占用时,线程不断循环检查(忙等),而不是挂起。

  • 适合锁占用时间很短的场景(避免线程上下文切换开销)。

  • 缺点:浪费 CPU 时间。

(6) 屏障(Barrier)

  • 一种同步点。

  • 多个线程必须都到达某个屏障位置,才能继续往下执行。

  • 常用于并行计算的阶段性同步。

Java CyclicBarrier,C++20 std::barrier


PCB —进程状态的标识

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────┐
PCB (进程档案)
├─────────────────┤
PID = 1234 身份信息
状态 = 就绪
PC = 0x8048123 程序计数器
SP = 0x7fff1234 栈指针
优先级 = 5
打开文件表指针
页表指针
└─────────────────┘

PCB作用:

  1. 进程切换
    • 内核保存当前进程的 PCB(寄存器内容、状态)。
    • 切换到另一个进程时,从该进程的 PCB 中恢复 CPU 状态。
  2. 进程管理
    • 操作系统通过 PCB 维护所有进程的运行状态(创建、调度、挂起、终止)。
  3. 进程通信

    • PCB 中保存通信所需的信息(管道、消息队列等)。

    直观类比

  • 进程 = 一个在跑的应用。
  • PCB = 操作系统里的“档案袋”,里面记录了这个进程的身份证号(PID)、运行现场(寄存器)、调度优先级、用的资源等。
  • 当操作系统切换进程时,就像“把档案袋收起来,换另一个档案袋”。

进程的调度算法 🚩

![[Pasted image 20251002113159.png]]

(1) 先来先服务(FCFS, First-Come-First-Served)

  • 类型:非抢占式
  • 规则:按照进程到达就绪队列的顺序分配 CPU。
  • 优点:简单、公平
  • 缺点:平均等待时间可能大,容易发生 短作业被长作业阻塞(Convoy Effect)

(2) 短作业优先(SJF, Shortest Job First)

  • 类型:非抢占式 / 可抢占式(称 SRTF, Shortest Remaining Time First)
  • 规则:优先执行预计运行时间最短的进程
  • 优点:平均等待时间最小(理论上最优)
  • 缺点:难以预测进程执行时间;可能导致长作业饥饿

(3) 时间片轮转(RR, Round Robin)

  • 类型:抢占式
  • 规则:每个进程按顺序分配固定时间片(quantum),时间片用完被剥夺 CPU → 排队等待下一轮
  • 优点:响应时间好,适合 时间共享系统
  • 缺点:时间片选择不合理会导致频繁切换(开销大)或响应慢

    (4) 优先级调度(Priority Scheduling)

  • 类型:可抢占或非抢占

  • 规则:给每个进程分配一个优先级,CPU 优先给优先级高的进程
  • 优点:重要任务优先处理
  • 缺点:低优先级可能 饥饿(Starvation)
  • 改进老化(Aging):随着等待时间增加,提高低优先级进程优先级

    (5) 多级队列调度(Multilevel Queue)

  • 类型:抢占或非抢占

  • 规则:将进程按类型(如交互型、批处理型)划分多个队列,每个队列有自己的调度策略
  • 优点:可针对不同进程类型优化调度
  • 缺点:队列划分固定,灵活性低

    (6) 多级反馈队列调度(Multilevel Feedback Queue)

  • 类型:抢占式

  • 规则:多个队列,进程可根据执行情况在队列间移动

    • 刚开始进程优先级高(时间片短)
    • 占用 CPU 时间长 → 移到低优先级队列
  • 优点:兼顾短作业响应快和长作业公平性

  • 缺点:实现复杂

僵尸进程和孤儿进程

1. 僵尸进程(Zombie Process)

(1)定义

  • 僵尸进程是已经 执行完毕(终止) 的进程,但其 PCB(进程控制块)仍然保留在内核中
  • 保留的原因是 父进程还没有读取子进程的退出状态(通过 wait() 系列系统调用)。

(2)出现场景

  • 父进程没有及时回收子进程的状态信息:
    pid_t pid = fork(); if (pid == 0) { exit(0); // 子进程退出 } else { sleep(1000); // 父进程没有 wait }

  • 此时子进程已经结束,但 PCB 还在内核中 → 僵尸进程。

(3)特点和作用

  • 特点

    1. 不占用 CPU(已经退出)
    2. 占用少量内存(PCB)
    3. 仍然有 PID(可以 ps 查看)
  • 作用

    • 保存退出状态,让父进程能获取子进程退出码(正常/异常)。

(4)解决方法

  1. 父进程主动回收

    • 使用 wait()waitpid() 读取子进程退出状态。
  2. 自动回收(避免僵尸)

    • 父进程捕获 SIGCHLD 信号,处理时调用 wait()
    • 在 Linux 可以设置:
      signal(SIGCHLD, SIG_IGN);

      系统会自动回收子进程 PCB。

(5)排查

  • ps -el | grep Z → 查看状态为 Z 的进程(僵尸)
  • top → STAT 列显示 Z

2. 孤儿进程(Orphan Process)

(1)定义

  • 孤儿进程父进程先于子进程结束 的进程。
  • 系统会把孤儿进程的 父进程改为 init 进程(PID=1),由 init 负责回收。

(2)出现场景

pid_t pid = fork(); if (pid > 0) { exit(0); // 父进程先结束 } else { sleep(100); // 子进程还在运行 }

  • 子进程就成为孤儿进程,被 init 收养。

(3)特点和作用

  • 特点

    1. 父进程消失,子进程仍运行
    2. init 进程接管
  • 作用

    • 保证操作系统可以最终回收子进程资源,不会出现永远占用内存的僵尸。

(4)解决方法

  • 孤儿进程不需要手动处理,由 init 自动回收。

(5)排查

  • ps -ef → PPID 列为 1 的进程通常是孤儿进程。
类型 定义 状态 占用 CPU 如何回收
僵尸进程 已退出,但 PCB 未被回收 Z 父进程调用 wait(),或由 SIGCHLD 处理自动回收
孤儿进程 父进程先退出的活着子进程 正常运行 init 收养,运行完毕由 init 回收
  • 僵尸过多 → PCB 占用过多 → 进程表满 → 新进程无法创建
  • 孤儿进程本身无害,由 init 管理