← 返回所有文章

SYSTEMS / 001//4 分钟阅读

进程与线程:程序是怎样同时运行的

从资源隔离和执行调度两个角度,理解进程、线程、并发与并行。

程序是磁盘上的指令与数据;进程是程序运行起来后的一个实例。打开两个相同的命令行程序,可以得到两个独立的进程,而不只是同一段程序「多执行了一次」。

资源与执行是两个视角

进程通常拥有独立的虚拟地址空间,以及文件描述符等资源。隔离意味着一个进程不能随意访问另一个进程的普通内存,但这不代表进程之间无法通过共享内存等机制通信。

线程是进程内部的一条执行路径。同一进程中的线程共享地址空间与许多资源,同时各自保有寄存器状态、栈等执行上下文。

观察角度进程同一进程内的线程
地址空间通常相互隔离共享
通信方式管道、套接字、共享内存等可以直接读写共享数据
出错影响通常限于当前进程可能影响整个进程
数据协调需要明确通信机制仍然需要同步

并发不一定并行

一个厨师交替处理多道菜,是并发;多个厨师在同一时刻各自做菜,是并行。前者强调任务在一段时间内共同推进,后者强调同一时刻实际执行多个任务。

即使只有一个 CPU 核心,操作系统也能通过调度让多个任务交替运行。切换发生得足够快,就会给人「同时运行」的感觉。多核提供了并行执行的条件,但程序能否有效利用,还取决于任务如何拆分。

共享状态为什么容易出错

假设两个线程都执行 count = count + 1。这句话可以拆成读取、计算和写回:

线程 A:读取 count = 0
线程 B:读取 count = 0
线程 A:写入 count = 1
线程 B:写入 count = 1

执行了两次加一,结果却可能只增加一次。这是理解竞态条件的直观例子。对于某些编程语言,未同步的数据竞争还可能直接构成未定义行为,不能只把它当作「偶尔少加一次」。

互斥锁、原子操作和消息传递可以帮助建立正确的协调关系。选择哪个机制,要从数据的所有权与访问顺序出发。

在线程数之前,先问任务是什么

  • 计算密集任务受 CPU 和算法效率影响,线程过多可能增加切换成本。
  • 等待网络或磁盘的任务,可以用线程池或异步 I/O 减少等待带来的空闲。
  • 共享数据越多,协调成本通常越高。先减少共享,再讨论锁的粒度。
# 查看 Linux 进程与线程
ps -eLf

# 查看一个指定进程的线程
ps -T -p 1234

理解进程与线程,关键是同时记住两件事:谁拥有资源,谁推进执行。把这两个问题分开,很多并发设计就会清晰起来。

继续阅读

EOF / 感谢阅读继续探索 ↗

搜索笔记

在全部文章中搜索