2. 进程与线程基础
各位同学,今天我们来聊聊并发编程最底层的两个概念——进程和线程。说实话,这两个东西我刚开始学的时候也绕了很久。后来在项目中踩过几次坑,才真正理解它们的区别。
先问大家一个问题:你打开电脑,同时听音乐、写文档、刷网页,操作系统是怎么让这么多程序“同时”运行的?
答案就在进程和线程里。
进程的概念:程序的一次“活”
进程是什么?简单说,就是程序的一次执行过程。你双击一个exe,操作系统就创建一个进程来运行它。
进程有自己的独立空间——内存、文件句柄、寄存器状态,谁也碰不到谁的。我当年做嵌入式开发时,一个进程崩溃了,其他进程纹丝不动。这就是进程隔离的好处。
每个进程包含:
- 代码段:程序指令
- 数据段:全局变量、静态变量
- 堆:动态分配的内存
- 栈:函数调用、局部变量
- PCB(进程控制块):操作系统管理进程的数据结构
核心要点:进程是资源分配的最小单位。每个进程都有独立的地址空间,切换开销大。
进程的生命周期:从生到死
进程不是一直运行的。它有几个状态,我习惯用“生老病死”来记:
- 创建(New):程序被加载到内存,PCB被创建
- 就绪(Ready):万事俱备,只欠CPU
- 运行(Running):CPU正在执行指令
- 阻塞(Blocked):等I/O、等锁、等信号
- 终止(Terminated):执行完毕或被杀死
嗯,这里要注意:就绪和阻塞的区别。就绪是“我能跑,但CPU没空理我”;阻塞是“我跑不了,得等某个事件”。
我曾经在调试一个网络服务时,发现进程一直卡在阻塞状态。查了半天,原来是socket没设置超时。那次之后,我写代码都会检查所有阻塞操作的超时机制。
线程的概念:进程里的“轻量级工人”
线程是什么?它是进程内的一个执行流。一个进程可以有多个线程,共享进程的资源。
说白了,进程是“工厂”,线程是“工人”。工厂(进程)提供场地和设备,工人(线程)在里面干活。
线程共享的东西:
- 代码段
- 数据段
- 堆
- 打开的文件
线程独有的东西:
- 栈(每个线程有自己的调用栈)
- 寄存器状态
- 线程ID
我的经验:线程切换比进程切换快得多。因为线程共享地址空间,切换时不需要切换页表。但共享也带来了麻烦——数据竞争。我见过太多因为多线程没加锁导致的bug,排查起来特别痛苦。
线程的优势:为什么我们需要它?
你想想看,如果每个任务都开一个进程,那系统开销得多大?线程的优势就在这里:
- 创建快:线程创建比进程快10-100倍
- 切换快:上下文切换开销小
- 通信方便:共享内存,不需要IPC
- 资源占用少:一个进程开100个线程,比开100个进程省太多
我记得有一次做高并发服务器,用进程模型,每个连接一个进程,结果内存直接爆了。换成线程池模型,同样的机器能支撑10倍以上的连接。
进程与线程的对比:一张表说清楚
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源拥有 | 独立地址空间 | 共享进程资源 |
| 创建开销 | 大(分配PCB、地址空间) | 小(只需分配栈和TCB) |
| 切换开销 | 大(切换页表、刷新TLB) | 小(只需切换寄存器) |
| 通信方式 | IPC(管道、消息队列、共享内存) | 直接读写共享变量 |
| 安全性 | 高(隔离性好) | 低(一个线程崩溃可能影响整个进程) |
| 适用场景 | 独立任务、安全性要求高 | 高并发、资源共享 |
避坑指南:我曾经在一个项目中,为了追求性能,把所有逻辑都塞进一个进程的多个线程里。结果一个线程的bug导致整个进程挂掉,所有用户都受影响。后来我学乖了——关键服务用进程隔离,非关键部分用线程提升效率。
用户态与内核态线程:两种实现方式
线程的实现有两种方式。嗯,这里有点绕,但很重要。
用户态线程(User-Level Threads)
线程的管理完全在用户空间完成,内核根本不知道有线程存在。每个进程自己维护一个线程表。
优点:
- 切换不需要系统调用,速度极快
- 可以自定义调度策略
- 不依赖操作系统
缺点:
- 一个线程阻塞(比如I/O),整个进程都阻塞
- 无法利用多核(内核只调度进程)
内核态线程(Kernel-Level Threads)
线程由操作系统内核管理。每个线程在内核中都有对应的TCB(线程控制块)。
优点:
- 一个线程阻塞,其他线程可以继续运行
- 可以利用多核CPU
- 内核负责调度,更公平
缺点:
- 切换需要系统调用,开销大
- 创建和销毁成本高
实际应用:现代操作系统(Linux、Windows)都使用内核态线程。但很多语言运行时(如Go的goroutine、Java的虚拟线程)在用户态实现了轻量级线程,结合了两者的优点。我个人觉得,理解这个区别对写出高性能并发代码很有帮助。
知识体系总览
下面这张图,我把本章的核心知识点串起来了。你可以看到进程和线程的关系,以及它们各自的生命周期和实现方式。
好了,这一章的内容就到这里。进程和线程是并发编程的基石,搞懂它们,后面的内容会轻松很多。
个人建议:学完理论后,最好动手写几个demo。比如用C语言写一个多进程程序和一个多线程程序,对比它们的创建时间和切换开销。实践出真知,这话一点不假。