1. 并发编程基础:进程、线程与协程

各位同学好,我是你们这门课的老朋友。今天咱们正式开篇,聊聊并发编程最底层的三个概念:进程、线程和协程。说实话,我见过太多人一上来就撸并发代码,结果线上服务各种卡死、数据错乱,最后回过头来才发现——连基础都没打牢。

这一章,咱们就把地基夯实。我会结合自己踩过的坑,把进程、线程、协程的区别,并发与并行的本质,以及Python里那个让人又爱又恨的GIL锁,一次性讲透。

1.1 进程:操作系统的“亲儿子”

进程是什么?说白了,就是程序的一次执行实例。你双击一个浏览器,操作系统就给你创建一个进程。每个进程都有自己独立的地址空间、内存、文件句柄——就像每个孩子都有自己的房间,互不干扰。

我记得刚入行时,有个同事写了个爬虫,开了几十个进程去抓数据。结果服务器直接OOM了。为什么?因为每个进程都有独立的内存空间,开销太大了。进程间切换,需要保存和恢复整个CPU上下文,成本很高。

进程的核心特征:

  • 资源隔离:一个进程挂了,不影响其他进程
  • 独立地址空间:进程间通信(IPC)需要管道、消息队列、共享内存等机制
  • 创建/销毁开销大:系统调用频繁,内存分配多

1.2 线程:轻量级的“打工仔”

线程是进程内部的执行单元。一个进程可以包含多个线程,它们共享进程的内存空间和资源。你想想看,如果每个任务都开一个进程,那服务器早炸了。线程就是来解决这个问题的——它们共享资源,切换成本低得多。

我做过一个高并发网关,最开始用多进程模型,每来一个请求就fork一个子进程。结果并发量一上来,CPU全耗在进程切换上了。后来改成多线程,吞吐量直接翻了三倍。嗯,这里要注意:线程虽然轻量,但共享内存也带来了数据竞争的问题,后面我们会讲锁机制。

对比维度 进程 线程
资源开销 大(独立内存空间) 小(共享进程资源)
切换成本 高(需要切换页表、刷新TLB) 低(只需保存寄存器)
数据共享 复杂(需要IPC机制) 简单(直接读写共享内存)
安全性 高(天然隔离) 低(一个线程崩溃可能拖垮整个进程)

1.3 协程:用户态的“微线程”

协程,很多人叫它“微线程”或“纤程”。它比线程更轻量,切换完全在用户态完成,不需要操作系统介入。说白了,就是程序员自己控制执行流的切换。

我曾经优化过一个爬虫服务,用线程池跑,并发200个任务,CPU就飙到80%了。后来改成协程,同样的机器,并发2000个任务,CPU才30%。为什么?因为协程切换就是保存几个寄存器和栈指针,开销几乎可以忽略。

我的建议:

IO密集型任务(网络请求、文件读写、数据库查询)优先考虑协程。CPU密集型任务(计算、加密、压缩)用多进程或线程池。别混用,否则调度起来很头疼。

1.4 并发 vs 并行:别再傻傻分不清

这两个词,面试必问,但很多人理解得模棱两可。我直接给你一个最直白的解释:

  • 并发(Concurrency):多个任务在同一个时间段内交替执行。单核CPU也能做到,靠的是时间片轮转。
  • 并行(Parallelism):多个任务在同一个时刻同时执行。必须有多核CPU支持。

你想想看,一个厨师(单核CPU)同时做三个菜,他只能切一下菜,炒一下锅,再回去切菜——这是并发。三个厨师(多核CPU)各做一道菜——这才是并行。

我在项目中遇到过很多次,代码里开了几十个线程,以为是在并行处理,结果部署到单核服务器上,性能反而下降了。为什么?因为线程切换本身就有开销,单核上并发太多,反而得不偿失。

避坑指南:

我曾经在一个2核4线程的机器上,给一个计算密集型任务开了8个线程。结果性能比单线程还差。后来用os.cpu_count()获取核心数,把线程数设为核心数+1,性能才正常。记住:并行需要硬件支持,别盲目开线程。

1.5 GIL锁:Python并发的心头痛

聊Python并发,绕不开GIL(Global Interpreter Lock,全局解释器锁)。这东西,说白了就是Python解释器里的一把大锁,保证同一时刻只有一个线程在执行Python字节码。

为什么会这样?因为Python的内存管理不是线程安全的。如果不加GIL,多个线程同时操作引用计数,内存分分钟就崩了。这是CPython实现的一个历史包袱。

我刚开始用Python做并发时,天真地以为多线程能利用多核。结果写了个CPU密集型的计算程序,开了4个线程,跑起来一看,CPU占用率只有100%(单核满载)。换成多进程,CPU才跑到400%。那一刻,我才真正理解了GIL的威力。

场景 多线程 多进程 协程
CPU密集型 ❌ 受GIL限制,无法利用多核 ✅ 每个进程有独立GIL,可并行 ❌ 协程本身不解决并行问题
IO密集型 ✅ 线程在IO等待时释放GIL ✅ 但进程开销大,不推荐 ✅ 最佳选择,开销最小
混合型 ⚠️ 部分场景可用 ✅ 配合进程池使用 ⚠️ 计算部分需要另开进程

GIL的真相:

GIL不是Python语言的限制,而是CPython解释器的实现限制。其他Python解释器(如Jython、IronPython)没有GIL。但CPython是主流,所以咱们得学会和GIL共存。

那怎么绕过GIL?我总结了三条路:

  1. 用多进程:每个进程有独立的GIL,适合CPU密集型任务。用multiprocessing模块,注意进程间通信的开销。
  2. 用协程:单线程内实现高并发,适合IO密集型任务。用asyncio,配合aiohttp、aiomysql等库。
  3. 用C扩展:把CPU密集型的部分用C/C++写,通过ctypes或cffi调用。在C代码里可以释放GIL。

我个人习惯是:IO密集型用协程,CPU密集型用多进程,实在不行再考虑C扩展。别一上来就上C扩展,维护成本太高。

1.6 本章知识体系总览

下面这张图,把本章的核心概念和关系梳理清楚了。建议你保存下来,后面学完整个课程再回来看,会有更深的理解。

并发编程基础概念体系 并发编程 进程 线程 协程 独立地址空间 共享进程资源 用户态切换 极低开销 并发(交替执行) 并行(同时执行) GIL:Python的全局解释器锁 多进程 协程 C扩展 IO密集型 → 协程优先