一、多线程基础:进程与线程的区别、Python中的GIL锁、threading模块入门

各位同学好,我是你们的老朋友。今天咱们聊聊多线程的入门知识。

说实话,我刚开始接触并发编程时,也被进程和线程搞晕过。后来在项目中踩了不少坑,才慢慢理清了头绪。今天我把这些经验分享给你。

1.1 进程 vs 线程:到底差在哪?

先问个问题:你打开浏览器、听音乐、写代码,这些程序是怎么同时运行的?

答案是操作系统在调度。每个运行的程序就是一个进程。而进程内部,还可以拆成更小的执行单元——线程。

对比项 进程 线程
资源开销 大(独立内存空间) 小(共享进程内存)
通信方式 IPC(管道、队列等) 直接读写共享变量
创建速度 慢 快
稳定性 一个进程挂了不影响其他 一个线程崩了可能拖垮整个进程

我个人的习惯是:IO密集型任务用多线程,CPU密集型任务用多进程。为什么?后面讲GIL时你就明白了。

核心区别一句话:进程是资源分配的最小单位,线程是CPU调度的最小单位。

1.2 Python的GIL锁:绕不开的坎

很多新手学Python多线程时,都会遇到一个奇怪的现象:开了8个线程,CPU占用率还是只有100%?

嗯,这就是GIL在作祟。

GIL(Global Interpreter Lock),全局解释器锁。它规定:同一时刻,只有一个线程能执行Python字节码。

为什么会这样?

说白了,Python的内存管理不是线程安全的。CPython解释器干脆加了一把大锁,谁拿到锁谁干活。其他线程?等着吧。

注意:GIL只影响CPU密集型任务。如果你的程序在等网络、等磁盘、等数据库,线程会主动释放GIL。所以多线程对IO密集型任务依然有效。

我曾经在一个爬虫项目里,用8个线程抓数据,速度提升了将近6倍。但换成纯计算任务,8个线程和1个线程几乎没区别。这就是GIL的威力。

1.3 threading模块入门:写个多线程程序

Python的threading模块,是标准库自带的。用起来很简单。

先看一个基础例子:

import threading
import time

def worker(name):
    print(f"线程 {name} 开始工作")
    time.sleep(2)
    print(f"线程 {name} 结束工作")

# 创建线程
t1 = threading.Thread(target=worker, args=("A",))
t2 = threading.Thread(target=worker, args=("B",))

# 启动线程
t1.start()
t2.start()

# 等待线程结束
t1.join()
t2.join()

print("所有线程执行完毕")

运行结果:

线程 A 开始工作
线程 B 开始工作
(等待2秒)
线程 A 结束工作
线程 B 结束工作
所有线程执行完毕

看到了吗?两个线程几乎是同时启动的。这就是并发执行的效果。

小技巧:用threading.current_thread().name可以获取当前线程的名字,调试时很有用。

1.4 线程生命周期:从创建到销毁

一个线程会经历这几个阶段:

  1. 新建(New):创建Thread对象,还没启动
  2. 就绪(Runnable):调用start(),等待CPU调度
  3. 运行(Running):获得CPU时间片,执行代码
  4. 阻塞(Blocked):等待IO、等待锁、调用sleep
  5. 死亡(Dead):run()执行完毕或抛出异常

我刚开始写多线程时,经常忘记调用join()。结果主线程跑完了,子线程还在后台跑。程序退出时,子线程被强制终止,数据都没保存。嗯,这个坑我踩过。

1.5 守护线程:后台的隐形打工人

有一种特殊的线程叫守护线程(Daemon Thread)。它会在主线程结束时自动退出。

import threading
import time

def background_task():
    while True:
        print("后台任务运行中...")
        time.sleep(1)

t = threading.Thread(target=background_task)
t.daemon = True  # 设置为守护线程
t.start()

time.sleep(3)
print("主线程结束,守护线程自动退出")

运行结果:

后台任务运行中...
后台任务运行中...
后台任务运行中...
主线程结束,守护线程自动退出

注意:守护线程不能执行关键任务。比如写文件、提交数据库事务。因为主线程退出时,它可能还没执行完。

1.6 本章知识体系

下面这张图,帮你理清本章的核心逻辑:

多线程基础:知识体系 进程 vs 线程 资源分配 vs CPU调度 GIL全局解释器锁 同一时刻只有一个线程执行 threading模块 创建、启动、等待线程 IO密集→多线程 CPU密集→多进程 守护线程自动退出 实践要点 • 用join()等待子线程完成 • 守护线程不能做关键操作

1.7 避坑指南

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

  • 忘记join():主线程跑完了,子线程还在后台跑。程序退出时数据丢失。
  • 共享变量不加锁:两个线程同时修改同一个列表,结果数据错乱。后面我们会讲锁的用法。
  • 误以为多线程一定能加速:CPU密集型任务,多线程反而可能更慢(GIL上下文切换开销)。

我曾经在一个数据处理项目里,用多线程做数值计算。开了16个线程,结果比单线程还慢。后来换成多进程,速度直接翻了8倍。嗯,这个教训很深刻。

好了,今天的内容就到这里。记住:多线程不是银弹,选对工具比盲目堆线程更重要。

可可精品资料库