一、核心基础
1. 线程核心概念
| 概念 | 定义 / 说明 |
|---|---|
| 线程 | 进程内最小执行单元(轻量级进程),共享进程资源,拥有独立执行栈和程序计数器 |
| 核心特点 | 共享资源、独立执行流、轻量级(创建 / 销毁开销小)、可抢占 / 协作 |
| 核心优势 | 提升 IO 密集型任务效率、后台处理耗时任务、优化 GUI 交互、简化通信(共享内存) |
| 线程分类 | 内核线程(OS 创建)、用户线程(用户程序实现) |
2. 线程 vs 进程
| 对比维度 | 线程 | 进程 |
|---|---|---|
| 资源分配 | 共享所属进程资源 | 独立分配资源(内存、CPU) |
| 开销 | 小(仅初始化执行栈) | 大(分配内存、加载程序) |
| 通信方式 | 直接读写共享变量(需同步) | 依赖 IPC(管道、套接字等) |
| 独立性 | 低(线程崩溃可能导致进程崩溃) | 高(进程崩溃不影响其他进程) |
| 调度单位 | 操作系统最小调度单元 | 操作系统最小资源分配单元 |
3. 核心模块
| 模块 | 定位 | 功能特点 | 推荐程度 |
|---|---|---|---|
_thread |
低级原始模块 | 基础线程创建,仅兼容保留,功能有限(无完善管理方法) | ❌ 不推荐 |
threading |
高级完善模块 | 包含_thread所有功能,支持线程管理、同步、辅助函数,功能全面 |
✅ 推荐 |
二、线程创建与启动(2 种核心方式)
1. 函数式创建(简单场景)
核心模板
import threading
import time
# 1. 定义线程执行函数
def task(name, count):
for i in range(count):
print(f"线程{name}:{i}")
time.sleep(0.5)
# 2. 创建线程(target=函数,args=参数元组)
t1 = threading.Thread(target=task, args=("A", 5))
t2 = threading.Thread(target=task, args=("B", 3))
# 3. 启动线程(必须用start(),不可直接调用函数)
t1.start()
t2.start()
# 4. 等待线程结束(主线程阻塞)
t1.join()
t2.join()
print("所有线程完成")
关键要点
args必须是元组类型,单个参数需加逗号(如(1,))- 启动线程用
start(),底层自动调用run()方法
2. 类继承式创建(复杂场景)
核心模板
import threading
import time
# 1. 自定义线程类,继承threading.Thread
class MyThread(threading.Thread):
# 2. 重写__init__,初始化线程属性
def __init__(self, name, count):
super().__init__() # 必须调用父类构造
self.name = name
self.count = count
# 3. 重写run(),定义线程执行逻辑
def run(self):
for i in range(self.count):
print(f"线程{self.name}:{i}")
time.sleep(0.5)
# 4. 创建并启动线程
t1 = MyThread("A", 5)
t2 = MyThread("B", 3)
t1.start()
t2.start()
# 5. 等待线程结束
t1.join()
t2.join()
print("所有线程完成")
关键要点
- 必须重写
run()方法(线程核心逻辑) - 无需手动调用
run(),start()会自动触发
三、线程管理核心能力
1. 核心方法与属性
| 方法 / 属性 | 功能描述 | 关键注意事项 |
|---|---|---|
start() |
启动线程,触发run() |
不可重复调用,否则报错 |
join(timeout) |
主线程等待该线程终止,timeout为超时时间(秒) |
无超时则一直阻塞 |
is_alive() |
判断线程是否运行(启动后、终止前返回True) |
常用于判断线程状态 |
daemon |
守护线程标志(True:主线程退出则自动终止) |
必须在start()前设置 |
getName()/setName() |
获取 / 设置线程名称 | 便于调试多线程程序 |
ident |
线程唯一标识符 | 只读属性,线程启动后生效 |
2. 辅助函数
| 函数名 | 功能描述 |
|---|---|
threading.current_thread() |
返回当前正在执行的线程对象 |
threading.enumerate() |
返回运行中的线程列表(不含未启动、已终止线程) |
threading.active_count() |
返回运行中线程数量,等同于len(threading.enumerate()) |
3. 重点:守护线程(daemon)
核心示例
import threading
import time
def daemon_task():
while True:
print("守护线程运行中...")
time.sleep(1)
# 设置为守护线程(必须在start()前)
t = threading.Thread(target=daemon_task, daemon=True)
t.start()
# 主线程运行3秒后退出,守护线程随之终止
time.sleep(3)
print("主线程退出")
应用场景
- 日志收集、监控、后台清理等辅助任务
- 无需独立于主线程存在的任务
四、线程同步与安全
1. 核心问题:线程不安全
- 原因:多线程共享数据时,非原子操作(如
num += 1)会导致数据错乱 - 示例:两个线程同时修改共享变量,最终结果小于预期
2. 解决方案 1:Lock/Rlock 锁
核心模板(Lock)
import threading
num = 0
lock = threading.Lock() # 创建锁对象
def add_num():
global num
for _ in range(100000):
lock.acquire() # 获取锁(阻塞直到锁释放)
num += 1 # 共享数据操作
lock.release() # 释放锁(必须执行,避免死锁)
t1 = threading.Thread(target=add_num)
t2 = threading.Thread(target=add_num)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"正确结果:{num}") # 200000
Lock vs Rlock
| 类型 | 特点 | 适用场景 |
|---|---|---|
Lock |
非可重入锁,同一线程多次acquire()会死锁 |
简单互斥场景(无嵌套加锁) |
Rlock |
可重入锁,同一线程可多次acquire(),需对应release() |
嵌套加锁场景(如函数调用函数) |
3. 解决方案 2:Queue 模块(线程安全队列)
核心优势
- 内置锁原语,无需手动加锁
- 支持线程间安全的数据传递与任务调度
三种队列类型
| 队列类型 | 特点 | 适用场景 |
|---|---|---|
queue.Queue |
先进先出(FIFO) | 按顺序处理任务(订单、消息) |
queue.LifoQueue |
后进先出(LIFO) | 优先处理最新任务(撤销操作) |
queue.PriorityQueue |
优先级队列((优先级, 数据)) |
紧急任务优先(告警、优先级任务) |
核心模板(FIFO 队列)
运行并比对两段代码的运行结果有什么不同:
q.join()不会等待生产者未来放入的任务,它只对调用时刻已在队列中的任务计数。当第一个任务被消费后,计数器归零,q.join()立即返回。
python
import queue
import threading
import time
# 创建队列(maxsize=5,满时阻塞)
q = queue.Queue(maxsize=5)
# 生产者:放数据
def producer():
for i in range(10):
q.put(f"数据{i}")
print(f"生产者放入:数据{i}")
time.sleep(0.5)
# 消费者:取数据
def consumer():
while True:
data = q.get() # 空时阻塞
print(f"消费者取出:{data}")
q.task_done() # 标记任务完成
time.sleep(1)
# 启动线程
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer, daemon=True)
t1.start()
t2.start()
q.join() # 等待队列所有任务完成
print("所有任务处理完毕")
python
import queue
import threading
import time
q = queue.Queue(maxsize=5)
def producer():
for i in range(10):
q.put(f"数据{i}")
print(f"生产者放入:数据{i}")
time.sleep(0.5)
print("生产者结束") # 用于验证生产者是否完成
def consumer():
while True:
data = q.get()
if data is None: # 退出信号
q.task_done()
break
print(f"消费者取出:{data}")
q.task_done()
time.sleep(1)
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer, daemon=True)
t1.start()
t2.start()
t1.join() # ✅ 先等待生产者结束(确保10个数据全部入队)
q.join() # ✅ 再等待队列清空
print("所有任务处理完毕")
核心方法
| 方法名 | 功能描述 |
|---|---|
put(item) |
入队,队列满时阻塞 |
get() |
出队,队列空时阻塞 |
empty()/full() |
判断队列空 / 满 |
task_done() |
标记任务完成,配合join()使用 |
join() |
阻塞主线程,直到队列所有任务都被task_done()标记 |
五、关键坑点与解决方案
1. GIL 锁的影响
- 什么是 GIL:CPython 的全局解释器锁,同一时间仅允许一个线程执行 Python 字节码
- 核心影响:
- IO 密集型任务:多线程效率提升明显(线程等待 IO 时释放 GIL)
- CPU 密集型任务:多线程无优势(甚至因切换开销下降)
- 解决方案:CPU 密集型用
multiprocessing(多进程)
2. 死锁问题
- 原因:多个线程互相等待对方释放锁(如线程 A 持有锁 1,等待锁 2;线程 B 持有锁 2,等待锁 1)
- 避免方法:
- 统一锁的获取顺序
- 使用
try-finally确保锁释放 - 用
Rlock替代Lock(适用于嵌套场景)
3. 线程启动误区
- 错误:直接调用
run()方法(等同于主线程同步执行,非多线程) - 正确:必须调用
start()方法(触发线程异步执行)
六、实战总结与口诀
1. 核心口诀
- 线程创建:函数式(简单)、类继承(复杂),启动必用
start() - 线程管理:
join()等待、daemon守护、is_alive()判状态 - 线程安全:共享数据用
Lock,任务调度用Queue - 场景选择:IO 密集用线程,CPU 密集用进程
2. 常见场景技术选型
| 场景类型 | 推荐技术 | 原因 |
|---|---|---|
| 文件读写、网络请求 | threading(多线程) |
IO 等待时释放 CPU,提升并发效率 |
| 数据计算、逻辑处理 | multiprocessing(多进程) |
绕过 GIL 锁,充分利用多核 CPU |
| 任务有序调度 | queue.Queue(FIFO 队列) |
线程安全,按顺序执行任务 |
| 紧急任务优先 | queue.PriorityQueue(优先级队列) |
按优先级分配资源,高优先级任务先执行 |
3. 复习重点
- 线程创建的两种方式及核心代码
- 线程同步的两种方案(Lock/Queue)
- GIL 锁的影响及场景选择
- 守护线程的作用与设置