前言
在 Python 开发中,多线程是提升程序效率、实现并发任务的核心技术,尤其适合 IO 密集型场景(文件读写、网络请求、爬虫等)。这篇文章从零带你吃透 Python 多线程:基础用法、线程通信、线程池、GIL 锁原理、常见问题解决方案,全程附带可直接运行的实战代码,新手也能轻松上手。
一、多线程核心概念
1. 什么是多线程?
多线程是同一个进程内 同时运行多个独立执行的线程,多个线程共享进程的内存、文件等资源,每个线程拥有独立的执行栈和调度权,能实现任务的并发执行。
简单说:一个程序(进程)可以同时干多件事,就是多线程。
1.1 多线程 VS 单线程
- 单线程:程序只有一条执行流,任务按顺序执行,逻辑简单,但无法利用多核 CPU,遇到阻塞任务会导致程序卡顿。场景:简单的顺序脚本、无需并发的小工具。
- 多线程:多条执行流同时运行,能充分利用 CPU 空闲时间,提升程序响应速度和并发能力。场景:爬虫、文件批量下载、接口并发请求、GUI 程序防卡顿。
1.2 Python 多线程的核心模块
Python 内置 threading 模块实现多线程编程,无需额外安装依赖,是 Python 多线程开发的标准工具。
二、threading 模块:线程的创建与基础管理
threading.Thread 是创建线程的核心类,我们先掌握线程的创建、启动、等待、标识四大基础操作。
2.1 基础创建线程
指定目标函数,创建线程对象,这是最常用的创建方式。
python
import threading
# 定义线程要执行的任务函数
def print_info():
for i in range(3):
print(f"子线程运行:{i}")
# 创建线程:target=任务函数,不要加括号!
thread = threading.Thread(target=print_info)
2.2 启动线程:start () 方法
调用 start() 才会真正启动线程,操作系统调度线程执行任务。
python
# 启动线程
thread.start()
# 主线程继续执行
print("主线程运行中...")
2.3 线程阻塞等待:join () 方法
join() 会让主线程等待子线程执行完毕后再继续,实现线程同步。
python
# 等待子线程执行完成
thread.join()
print("子线程执行完毕,主线程继续!")
2.4 线程标识:name/ident 属性
给线程命名、获取唯一 ID,方便多线程调试和管理。
python
# 创建线程时指定名称
thread = threading.Thread(target=print_info, name="测试线程")
thread.start()
# 获取线程名称和唯一标识符
print("线程名称:", thread.name)
print("线程ID:", thread.ident)
三、线程间通信与同步:解决数据安全问题
多线程共享进程资源,多个线程同时修改共享数据会引发数据错乱,必须通过同步机制保证线程安全。
3.1 Lock 互斥锁:保证共享数据安全
threading.Lock 是最基础的互斥锁,同一时间只有一个线程能获取锁,其他线程等待,避免竞态条件。
推荐使用 with 语法,自动加锁 / 解锁,无需手动释放。
python
import threading
# 共享变量
total = 0
# 创建互斥锁
lock = threading.Lock()
def add_num():
global total
# 加锁:with语法自动管理锁
with lock:
for _ in range(100000):
total += 1
# 创建5个线程
threads = [threading.Thread(target=add_num) for _ in range(5)]
# 启动所有线程
for t in threads:
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print("最终结果:", total) # 结果一定是500000,线程安全
3.2 Queue 队列:线程间安全通信
queue.Queue 是线程安全的队列,专门用于线程间数据传递,内置锁机制,无需手动加锁。
python
import threading
import queue
# 生产者:向队列放数据
def producer(q):
for i in range(5):
q.put(f"数据{i}")
print(f"生产:数据{i}")
# 消费者:从队列取数据
def consumer(q):
while not q.empty():
data = q.get()
print(f"消费:{data}")
# 创建队列
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()
优势:
- 自动管理线程创建 / 销毁,降低资源消耗
- 控制并发数量,避免线程过多导致程序崩溃
- 支持获取任务返回值,使用更灵活
四、线程池:高效管理大量线程
手动创建大量线程会浪费系统资源,线程池可以复用线程、控制最大并发数,是企业开发的首选方案。
Python 内置 concurrent.futures.ThreadPoolExecutor 实现线程池。
python
from concurrent.futures import ThreadPoolExecutor
import time
# 定义任务函数
def task(name):
print(f"任务{name}开始执行")
time.sleep(2)
return f"任务{name}执行完成"
# 创建线程池:max_workers=最大线程数
with ThreadPoolExecutor(max_workers=3) as executor:
# 提交任务到线程池
future1 = executor.submit(task, 1)
future2 = executor.submit(task, 2)
future3 = executor.submit(task, 3)
# 获取任务返回结果
print(future1.result())
print(future2.result())
print(future3.result())
五、必知:全局解释器锁(GIL)对多线程的影响
很多新手会疑惑:Python 多线程为什么不能充分利用多核 CPU? 核心原因就是 GIL(全局解释器锁)。
5.1 GIL 是什么?
GIL 是 CPython 解释器的互斥锁 ,它的规则:同一时刻,同一个进程内只有一个线程能执行 Python 字节码。
也就是说:Python 多线程是并发 (交替执行),不是并行(同时执行)。
5.2 GIL 对程序的影响
- 计算密集型任务 :多线程效率极低,GIL 会成为性能瓶颈,推荐用多进程替代。
- IO 密集型任务:多线程效率大幅提升!因为线程在等待 IO(网络、文件)时,会自动释放 GIL,其他线程可以执行。
5.3 解决方案
- IO 密集型:继续用多线程(threading / 线程池)
- 计算密集型:使用
multiprocessing多进程 - 高性能异步:使用
asyncio异步编程
六、多线程常见问题与解决方案
多线程编程最容易踩坑,掌握这两个高频问题,开发少走弯路。
6.1 死锁(Deadlock):线程互相等待锁
死锁原因
两个线程各自持有一个锁,同时等待对方释放锁,导致程序无限阻塞。
示例(死锁代码):
python
import threading
lockA = threading.Lock()
lockB = threading.Lock()
def func1():
lockA.acquire()
lockB.acquire()
lockB.release()
lockA.release()
def func2():
lockB.acquire()
lockA.acquire()
lockA.release()
lockB.release()
# 运行后程序卡死,发生死锁
threading.Thread(target=func1).start()
threading.Thread(target=func2).start()
死锁解决方法
- 统一锁的获取顺序(最常用)
- 使用
timeout超时机制 - 避免嵌套锁
修复死锁后的代码:
python
# 统一先获取lockA,再获取lockB
def func1():
with lockA:
with lockB:
pass
def func2():
with lockA:
with lockB:
pass
6.2 守护线程:程序退出自动关闭子线程
默认情况下,主线程退出后,子线程会继续执行。如果希望主线程退出,子线程直接关闭,设置守护线程。
python
import threading
import time
def daemon_task():
while True:
print("守护线程运行中...")
time.sleep(1)
# 设置 daemon=True 成为守护线程
t = threading.Thread(target=daemon_task, daemon=True)
t.start()
time.sleep(3)
print("主线程退出,守护线程自动关闭")