一、认识多线程:线程、进程与GIL的那些事
大家好,我是你们的老朋友。今天咱们来聊聊多线程。
说实话,我刚入行那会儿,对多线程也是一头雾水。总觉得「线程」和「进程」差不多,不就是个并发嘛。直到有一次,我在一个文件处理系统里用了多线程,结果性能反而下降了……嗯,那次教训挺深刻的。
所以今天,我带你从头捋一遍。把基础打牢,后面实战才不会翻车。
1.1 什么是线程?
线程,说白了就是「轻量级的执行单元」。你可以把进程想象成一个工厂车间,线程就是车间里的工人。工人共享车间的资源(比如工具、材料),但各自干各自的活。
一个进程里可以有一个或多个线程。它们共享进程的内存空间,所以通信起来很方便——但也容易出问题,这个后面再说。
核心要点:线程是CPU调度的最小单位。进程是资源分配的最小单位。
1.2 进程 vs 线程:到底有什么区别?
我习惯用一个比喻来解释:
- 进程 = 一个独立的程序,比如你打开的Chrome浏览器。每个进程有自己的内存空间、文件句柄、环境变量。进程之间互相隔离,一个崩了不影响另一个。
- 线程 = 进程里的一个执行流。比如Chrome里一个标签页可能对应一个线程。线程共享进程的内存,所以切换开销小,但一个线程挂了可能拖垮整个进程。
来看个表格,更直观:
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源开销 | 大(独立内存、文件等) | 小(共享进程资源) |
| 切换速度 | 慢(需要切换页表等) | 快(只需切换寄存器) |
| 通信方式 | IPC(管道、消息队列等) | 直接读写共享变量 |
| 安全性 | 高(隔离性强) | 低(容易数据竞争) |
| 适用场景 | CPU密集型、需要隔离的任务 | I/O密集型、需要频繁交互的任务 |
你想想看,如果你要写一个文件批量处理的工具,每个文件独立处理,用多进程更安全。但如果你要同时下载多个文件,用多线程更轻量。
1.3 Python中的GIL锁:绕不开的坎
好,重点来了。Python的GIL(全局解释器锁),是很多人的噩梦。
GIL是什么?简单说,就是CPython解释器里的一把大锁。它保证同一时刻只有一个线程在执行Python字节码。
为什么会这样?因为Python的内存管理不是线程安全的。如果不加GIL,多个线程同时操作引用计数,内存就乱套了。
注意:GIL只影响CPU密集型任务。对于I/O密集型任务,线程在等待I/O时会释放GIL,所以多线程依然有效。
我曾经在一个数据清洗项目里,用多线程做CPU密集的数值计算。结果8个线程跑起来,CPU利用率只有100%——相当于单核在干活。后来换成多进程,CPU直接飙到800%。
所以,记住这个原则:
- CPU密集型(比如数学计算、图像处理)→ 用多进程
- I/O密集型(比如文件读写、网络请求)→ 用多线程
1.4 多线程适用场景:什么时候该用它?
我个人的经验是,多线程最适合以下场景:
- 文件批量读写:比如同时读取100个日志文件,每个文件读取时都在等磁盘I/O,线程可以在这段时间切换。
- 网络爬虫:发请求等响应时,线程可以干别的。
- GUI应用:主线程处理界面,后台线程做耗时操作,防止界面卡死。
- 数据库批量操作:同时执行多个查询,每个查询都在等数据库返回。
但要注意,多线程不是银弹。如果你要处理的任务本身就不需要等待(比如纯CPU计算),多线程反而因为GIL而变慢。
小技巧:判断是否适合多线程,就看任务里「等待时间」占比高不高。如果等待时间超过50%,多线程大概率有收益。
1.5 本章知识体系图
下面我用一张SVG图,把本章的核心逻辑串起来。你看完应该能一目了然:
这张图把进程、线程、GIL和适用场景串在了一起。你顺着箭头看,就能理解它们之间的关系。
1.6 避坑指南
最后,分享几个我踩过的坑:
- 别在CPU密集型任务里用多线程——我曾经用8个线程做图像滤波,结果比单线程还慢。因为GIL导致线程频繁切换,开销反而更大。
- 注意共享数据的保护——多线程共享变量时,记得加锁。我见过一个日志系统,多个线程同时写同一个文件,结果日志内容乱成一团。
- 线程数不是越多越好——线程太多,上下文切换开销会吃掉所有收益。一般建议线程数 = CPU核心数 × (1 + 等待时间/计算时间)。
好了,这一章就到这里。基础打牢了,下一章我们开始动手写代码——用多线程真正提升文件读写性能。