第一章 并发编程基础:线程、进程与GIL

说实话,我刚接触并发编程时,也觉得这玩意儿离业务代码挺远的。

直到有一次线上服务「卡死」,排查了整整一个下午,才发现是某个线程池配置不当引发的死锁。嗯,从那天起我才真正意识到——并发不是加分题,而是必修课。

进程与线程:两条腿走路

进程和线程,说白了就是操作系统的两种「调度单位」。

进程是个「重量级」的东西。每个进程都有自己的内存空间、文件描述符、堆栈。进程之间的通信,得靠管道、消息队列、共享内存这些机制。我印象特别深——早年做一个图像处理服务,一上来就用多进程,结果内存直接飙到两三个GB。后来换了线程池,内存占用降了一半多。

线程就轻巧多了。线程共享进程的内存空间,创建和销毁的代价小得多。同一个进程里的线程,可以共用全局变量、堆上的对象。注意,这里有个坑——共享也意味着竞争。

我把它们的主要区别整理了一下:

维度 进程 线程
内存开销 独立内存空间,大 共享进程内存,小
创建/销毁速度 慢(需要分配新内存空间) 快(复用进程资源)
通信方式 IPC(管道、信号量、共享内存等) 直接读写共享变量
隔离性 强,一个进程崩溃不影响其他 弱,一个线程挂掉可能拖垮整个进程
适用场景 CPU密集、安全隔离要求高 I/O密集、共享数据频繁
小建议:做Python项目时,我习惯把I/O密集的任务用多线程,CPU密集的任务用多进程。这个选择跟后面的GIL有直接关系。

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+。

避坑指南:我曾经在数据处理脚本里用多线程处理1000个文件。数据量大,每个文件解析0.2秒。改成多进程后,时间从200秒降到了不到60秒。如果是纯计算,别用多线程,用多进程或者协程。

并发与并行:一字之差,天壤之别

这两个概念经常被混着用。我当年踩过这个坑——面试时被问到,差点答成一样的。

  • 并发:多个任务在同一时间段内交替执行。单核CPU也能实现,靠的是时间片切换。
  • 并行:多个任务在同一时刻真正同时执行。必须有多个CPU核心。

你想象一下:你在写代码,邮件在后台下载,音乐在播放。这是并发。如果是你和同事各自写各自的,互不干扰,那是并行。

用四个字概括:并发是逻辑上的同时,并行是物理上的同时。

GIL导致Python的多线程没法实现真正的并行。但多进程可以——每个进程有自己的GIL,各跑各的。这也是为什么我们在做高性能计算时,通常会选多进程,或者干脆换Go、Java。

懂了这些概念,你再去配置连接池、调优线程数,心里就有底了。

核心总结:
  • 进程胖但稳,线程瘦但快。
  • GIL卡住了多线程的并行能力,但卡不住I/O密集型任务。
  • 并发是抽象概念,并行是硬件能力。
  • 选多线程还是多进程,先问一句——你的任务在等CPU,还是在等外部资源?
并发编程基础核心概念图 进程 线程 GIL 并发 并行 多线程 多进程 理解底层机制,才能做对连接池的配置决策 ——这是后续所有优化章节的基础