Python3 多线程详解
Python 的多线程就像一个工厂里的多条生产线 —— 同一个厂房(进程)里,多条生产线(线程)可以同时干活,共享厂房里的工具(内存资源),大大提高工作效率。今天咱们用大白话详解 Python3 的多线程,从基础到实战,看完就能用。
一、先搞懂:什么是线程?和进程有啥区别?
假设你打开一个微信(这是一个进程),微信里同时进行着:
- 接收消息(一个任务)
- 下载图片(另一个任务)
- 播放语音(第三个任务)
这些同时运行的 “小任务” 就是线程。
线程 vs 进程:
- 进程是 “独立的工厂”,每个进程有自己的内存空间(比如微信和 QQ 是两个进程,内存不共享);
- 线程是 “工厂里的生产线”,属于同一个进程,共享内存(微信里的接收消息和下载图片线程,共享微信的内存数据);
- 线程更 “轻量”:启动 / 切换线程比启动 / 切换进程快得多,适合做 “同时处理多个小任务” 的场景。
二、Python 多线程的 “工具箱”:threading 模块
Python3 通过内置的
threading模块实现多线程,替代了旧版的thread模块。最核心的是Thread类,用它就能创建线程。1. 创建线程的两种方式
方式一:直接传 “要执行的任务”(函数)
先定义一个普通函数作为 “任务”,然后用
Thread包装起来启动: import threading
import time
# 定义一个任务函数:打印数字
def print_numbers(name):
for i in range(5):
print(f"线程{name}:{i}")
time.sleep(0.5) # 模拟任务耗时
# 创建两个线程,分别执行print_numbers任务
t1 = threading.Thread(target=print_numbers, args=("A",)) # args是传给函数的参数
t2 = threading.Thread(target=print_numbers, args=("B",))
# 启动线程(开始干活)
t1.start()
t2.start()
# 等待线程结束(主线程等子线程干完再走)
t1.join()
t2.join()
print("所有线程执行完毕!")
运行后会看到 A 和 B 线程交替打印,说明它们在 “同时” 工作。
方式二:继承 Thread 类,重写 run () 方法
如果任务逻辑复杂,可以把线程封装成一个类:
import threading
import time
# 继承Thread类
class MyThread(threading.Thread):
# 重写run()方法:线程启动后会自动执行这里的代码
def run(self):
for i in range(5):
print(f"线程{self.name}:{i}") # self.name是线程名(可自定义)
time.sleep(0.5)
# 创建线程(可以指定name参数给线程起名)
t1 = MyThread(name="A")
t2 = MyThread(name="B")
t1.start()
t2.start()
t1.join()
t2.join()
print("所有线程执行完毕!")
两种方式效果一样,实际开发中第一种更简洁(适合简单任务),第二种更灵活(适合复杂逻辑)。
2. 线程的核心操作
start():启动线程(让线程开始执行任务);join():让主线程 “等待” 该线程执行完毕后再继续(比如上面的例子,主线程会等 t1 和 t2 都跑完才打印 “所有线程执行完毕”);is_alive():判断线程是否还在运行(返回 True/False);name:线程的名字(可通过Thread(name="xxx")设置,方便调试);daemon:守护线程(后台线程)。如果设置daemon=True,主线程结束时,不管守护线程是否执行完都会被强制终止(适合日志、监控等 “不重要” 的后台任务)。
示例:守护线程
import threading
import time
def background_task():
while True:
print("后台监控中...")
time.sleep(1)
# 创建守护线程(daemon=True)
t = threading.Thread(target=background_task, daemon=True)
t.start()
# 主线程只运行3秒
time.sleep(3)
print("主线程结束,守护线程会被强制终止")
运行后会看到:主线程 3 秒后结束,守护线程的 “监控” 也会跟着停。
三、坑点:线程安全问题(多个线程抢资源怎么办?)
多个线程共享数据时,可能会出问题。比如两个线程同时给一个变量 “加 1”,结果可能不对:
import threading
count = 0 # 共享变量
def add():
global count
for _ in range(100000):
count += 1 # 看似简单的加1,实际是"读-改-写"三步操作
# 两个线程同时执行add
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"最终结果:{count}") # 预期200000,实际可能小于200000
原因:当 t1 刚读完 count 的值(比如 100),还没来得及改,t2 也读到了 100,两者都加 1 后写回,结果本该是 102,却变成了 101—— 这就是 “竞态条件”(多个线程抢资源导致数据错乱)。
解决:用 “锁”(Lock)保护共享资源
就像厕所需要锁一样,共享资源也需要 “锁”:一个线程用的时候上锁,用完了解锁,其他线程必须等解锁后才能用。
import threading
count = 0
lock = threading.Lock() # 创建一把锁
def add():
global count
for _ in range(100000):
lock.acquire() # 上锁(如果被占用,就等待)
count += 1
lock.release() # 解锁(让其他线程可以用)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"最终结果:{count}") # 这次一定是200000
注意:
- 锁不要滥用,否则会让多线程变成 “串行”(和单线程一样慢);
- 锁要配对使用(acquire 后必须 release),否则会导致 “死锁”(所有线程都在等对方解锁,卡死)。
四、Python 多线程的 “特殊限制”:GIL 全局解释器锁
这是 Python 多线程最容易踩的坑:在 CPU 密集型任务中,多线程可能比单线程还慢。
原因是 Python 有个 “GIL(全局解释器锁)”:不管你开多少线程,同一时间只有一个线程能执行 Python 字节码(相当于一个工厂只有一个总开关,同一时间只能开一条生产线)。
- IO 密集型任务(比如网络请求、文件读写):多线程很有用!因为 IO 操作时线程会释放 GIL(等待数据时不占着开关),其他线程可以干活;
- CPU 密集型任务(比如大量计算):多线程没用!因为线程一直在抢 GIL,切换还会浪费时间,这时应该用
multiprocessing多进程(绕过 GIL 限制)。
五、线程间通信:用 Queue 安全传数据
线程间尽量不要直接操作共享变量(容易出安全问题),推荐用
queue.Queue(线程安全的队列)传递数据: import threading
import queue
import time
# 生产者线程:往队列里放数据
def producer(q):
for i in range(5):
data = f"数据{i}"
q.put(data) # 放数据
print(f"生产了:{data}")
time.sleep(1)
q.put(None) # 放一个"结束信号"
# 消费者线程:从队列里拿数据
def consumer(q):
while True:
data = q.get() # 拿数据(如果队列空,会等待)
if data is None:
break # 收到结束信号,退出
print(f"消费了:{data}")
time.sleep(2)
# 创建一个线程安全的队列
q = queue.Queue()
t1 = threading.Thread(target=producer, args=(q,))
t2 = threading.Thread(target=consumer, args=(q,))
t1.start()
t2.start()
t1.join()
t2.join()
print("完成")
队列的
put()和get()方法自带锁机制,不用手动加锁,安全又方便。六、总结:多线程该怎么用?
- 适用场景:IO 密集型任务(爬虫、文件处理、网络请求等);
- 避坑点:
- 多线程共享数据必须加锁(Lock);
- CPU 密集型任务优先用多进程(
multiprocessing); - 线程通信优先用
queue.Queue,少用共享变量;
- 核心工具:
threading.Thread创建线程,start()启动,join()等待,Lock保证安全,Queue传递数据。
浙公网安备 33010602011771号