一、认识多线程:线程、进程与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 多线程适用场景:什么时候该用它?

我个人的经验是,多线程最适合以下场景:

  1. 文件批量读写:比如同时读取100个日志文件,每个文件读取时都在等磁盘I/O,线程可以在这段时间切换。
  2. 网络爬虫:发请求等响应时,线程可以干别的。
  3. GUI应用:主线程处理界面,后台线程做耗时操作,防止界面卡死。
  4. 数据库批量操作:同时执行多个查询,每个查询都在等数据库返回。

但要注意,多线程不是银弹。如果你要处理的任务本身就不需要等待(比如纯CPU计算),多线程反而因为GIL而变慢。

小技巧:判断是否适合多线程,就看任务里「等待时间」占比高不高。如果等待时间超过50%,多线程大概率有收益。

1.5 本章知识体系图

下面我用一张SVG图,把本章的核心逻辑串起来。你看完应该能一目了然:

多线程基础认知框架 进程 资源分配最小单位 线程 CPU调度最小单位 包含多个 GIL(全局解释器锁) 同一时刻只有一个线程执行Python字节码 多线程适用场景 I/O密集型任务(文件读写、网络请求、数据库操作) CPU密集型任务请用多进程 核心原则:等待时间占比高 → 多线程有效

这张图把进程、线程、GIL和适用场景串在了一起。你顺着箭头看,就能理解它们之间的关系。

1.6 避坑指南

最后,分享几个我踩过的坑:

  • 别在CPU密集型任务里用多线程——我曾经用8个线程做图像滤波,结果比单线程还慢。因为GIL导致线程频繁切换,开销反而更大。
  • 注意共享数据的保护——多线程共享变量时,记得加锁。我见过一个日志系统,多个线程同时写同一个文件,结果日志内容乱成一团。
  • 线程数不是越多越好——线程太多,上下文切换开销会吃掉所有收益。一般建议线程数 = CPU核心数 × (1 + 等待时间/计算时间)。

好了,这一章就到这里。基础打牢了,下一章我们开始动手写代码——用多线程真正提升文件读写性能。

可可精品资料库