进程与线程

"进程是资源分配的单位,线程是 CPU 调度的单位"——这句经典论断几乎出现在每场操作系统面试里。本文讲清进程与线程的区别、进程的状态变化,以及语言层面的对应实现。

什么是进程

进程是正在运行的程序实例:程序是静态的代码与数据,进程是动态的执行过程。每个进程拥有独立的地址空间、文件描述符、寄存器现场等资源,进程之间默认相互隔离。

┌─────────────────────────┐
│  进程 A:独立的地址空间    │   进程 B 看不到 A 的内存,
│  代码 | 数据 | 堆 | 栈   │   互不干扰,一个崩溃不影响另一个
└─────────────────────────┘

Linux 下用 fork() 创建子进程:子进程是父进程的一份"拷贝",之后各自独立运行。

#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();   // 父进程返回子进程 pid,子进程返回 0
    if (pid == 0) {
        printf("我是子进程\n");
    } else {
        printf("我是父进程,子进程 pid=%d\n", pid);
    }
    return 0;
}
// 输出:我是父进程,子进程 pid=1234
//       我是子进程(两行顺序不定)

什么是线程

线程是进程内部的"一条执行流"。一个进程可以包含多个线程,它们共享该进程的地址空间与资源,所以创建和切换的开销远小于进程,但也因此引入了同步问题。

对比项进程线程
资源独立地址空间共享进程资源
创建/切换成本
相互影响隔离,崩溃不连坐共享数据需加锁
通信方式需 IPC 机制直接读写共享变量
拥有者拥有资源拥有 CPU 执行权(栈、寄存器)

进程的三种基本状态

进程在生命周期中不停地在状态间切换:

新建 → 就绪 ⇄ 运行 → 阻塞
         ↑           │
         └───────────┘(等待的事件完成,回到就绪)
  • 就绪:万事俱备,只等 CPU;
  • 运行:正在 CPU 上执行;
  • 阻塞:等待 I/O 或某事件(如等待键盘输入、等待锁)。

面试高频点:就绪 → 运行叫调度;运行 → 就绪叫抢占(时间片耗尽或被更高优先级打断);运行 → 阻塞是进程自己发起 I/O。

语言中的线程与协程

  • Javanew Thread(() -> {}).start(),JVM 线程通常映射到操作系统线程。
  • Pythonthreading 模块,但受 GIL 限制,CPU 密集任务应改用多进程。
  • Go:goroutine 是用户态协程,由 Go 运行时调度到少量 OS 线程上,可轻松开上万个。
// 以下片段需放入 main 函数中运行:goroutine 基本用法
go func() { fmt.Println("协程 1") }()
go func() { fmt.Println("协程 2") }()
time.Sleep(time.Millisecond) // 简单等待协程执行完毕
// 输出:协程 1 与协程 2,顺序不定

小结:进程负责"圈地"(资源隔离),线程负责"干活"(CPU 执行)。要隔离用进程,要高并发低开销用线程或协程;而"共享内存带来的同步问题",正是后续进程间通信与锁相关内容的引子。

笔记加载中…