第一章 并发编程基础:线程、进程与GIL
说实话,我刚接触并发编程时,也觉得这玩意儿离业务代码挺远的。
直到有一次线上服务「卡死」,排查了整整一个下午,才发现是某个线程池配置不当引发的死锁。嗯,从那天起我才真正意识到——并发不是加分题,而是必修课。
进程与线程:两条腿走路
进程和线程,说白了就是操作系统的两种「调度单位」。
进程是个「重量级」的东西。每个进程都有自己的内存空间、文件描述符、堆栈。进程之间的通信,得靠管道、消息队列、共享内存这些机制。我印象特别深——早年做一个图像处理服务,一上来就用多进程,结果内存直接飙到两三个GB。后来换了线程池,内存占用降了一半多。
线程就轻巧多了。线程共享进程的内存空间,创建和销毁的代价小得多。同一个进程里的线程,可以共用全局变量、堆上的对象。注意,这里有个坑——共享也意味着竞争。
我把它们的主要区别整理了一下:
| 维度 | 进程 | 线程 |
|---|---|---|
| 内存开销 | 独立内存空间,大 | 共享进程内存,小 |
| 创建/销毁速度 | 慢(需要分配新内存空间) | 快(复用进程资源) |
| 通信方式 | IPC(管道、信号量、共享内存等) | 直接读写共享变量 |
| 隔离性 | 强,一个进程崩溃不影响其他 | 弱,一个线程挂掉可能拖垮整个进程 |
| 适用场景 | CPU密集、安全隔离要求高 | I/O密集、共享数据频繁 |
Python的GIL:一块甩不掉的枷锁
GIL全称是Global Interpreter Lock,全局解释器锁。
很多刚入行的朋友问我:Python的多线程是不是假的?
我说——不是假的,但有代价。GIL保证同一时刻只有一个线程在执行Python字节码。你开十个线程,CPU核心就在那里排队干活。也就是说,多线程对CPU密集型任务基本没有加速效果。
为什么会这样?
Python的内存管理不是线程安全的。如果没有GIL,一个线程释放对象,另一个线程正好访问它,程序立马崩。CPython选择了一个简单的方案——加一把大锁,全锁住。你可以说它保守,但它稳。
那GIL下,多线程就完全没用了吗?
当然不是。你想想看,如果任务在等待I/O——比如读文件、发HTTP请求、查数据库——线程会主动释放GIL。这个时候,其他线程就能跑。所以,对I/O密集型场景,多线程依旧有效。我个人做过一个压测:用Python写了一个简单的Web服务器,单线程每秒处理300个请求,换成4个线程干到了1100+。
并发与并行:一字之差,天壤之别
这两个概念经常被混着用。我当年踩过这个坑——面试时被问到,差点答成一样的。
- 并发:多个任务在同一时间段内交替执行。单核CPU也能实现,靠的是时间片切换。
- 并行:多个任务在同一时刻真正同时执行。必须有多个CPU核心。
你想象一下:你在写代码,邮件在后台下载,音乐在播放。这是并发。如果是你和同事各自写各自的,互不干扰,那是并行。
用四个字概括:并发是逻辑上的同时,并行是物理上的同时。
GIL导致Python的多线程没法实现真正的并行。但多进程可以——每个进程有自己的GIL,各跑各的。这也是为什么我们在做高性能计算时,通常会选多进程,或者干脆换Go、Java。
懂了这些概念,你再去配置连接池、调优线程数,心里就有底了。
- 进程胖但稳,线程瘦但快。
- GIL卡住了多线程的并行能力,但卡不住I/O密集型任务。
- 并发是抽象概念,并行是硬件能力。
- 选多线程还是多进程,先问一句——你的任务在等CPU,还是在等外部资源?