1. 代码的“等待焦虑”:单线程的痛点 🕒
关键字
:单线程、阻塞、等待时间 内容详解: 普通代码是“单线程”的——像“一个人干所有活”:如果有个任务要等30秒(比如查数据库、下载文件),整个程序会“卡着等”,啥也干不了。 比如Web应用里,用户请求一个“生成大数据报表”的接口,报表要算30秒——这期间其他用户的请求都会被卡住,体验超差! 类比:你做饭时“先等水烧开(3分钟),再切菜”——单线程是“站在锅边干等3分钟”;而
线程
是“烧水的同时切菜”,并行做事,不浪费时间!
二、线程基础:啥是线程?怎么用?
2. 线程入门:让代码“分身干活” 🧵
关键字
:线程、threading模块、并行执行 内容详解: Python用
threading
模块创建线程——让一个程序同时跑多个“子任务”。比如“一边下载文件,一边显示进度”:
import threading
import time
# 任务1:模拟“耗时工作”(比如查数据库)
def long_task(task_name):
print(f"{task_name} 开始执行,需要3秒...")
time.sleep(3) # 模拟等待3秒
print(f"{task_name} 执行完成!")
# 单线程方式:卡着等
print("=== 单线程模式 ===")
long_task("任务A")
long_task("任务B") # 等任务A完成才会开始,总共花6秒
# 多线程方式:并行执行
print("\n=== 多线程模式 ===")
# 创建线程对象,指定要执行的函数
thread_a = threading.Thread(target=long_task, args=("任务A",))
thread_b = threading.Thread(target=long_task, args=("任务B",))
# 启动线程
thread_a.start()
thread_b.start()
# 等待所有线程完成(可选)
thread_a.join()
thread_b.join() # 两个任务并行,总共花3秒
多线程下,任务A和B同时跑,原本6秒的工作3秒就做完了——这就是线程的价值!
三、Web场景:用线程解决“用户等待”问题
3. 实战:Flask里用线程处理“耗时请求” 🕸️
关键字
:Flask线程、后台任务、用户体验 内容详解: Web应用中,耗时任务(比如生成报表)如果用单线程,用户会看到“加载中”卡30秒。用线程把任务丢到“后台”,让用户立刻得到响应:
from flask import Flask, jsonify
import threading
import time
app = Flask(__name__)
# 耗时的后台任务
def generate_report(user_id):
print(f"开始给用户{user_id}生成报表...")
time.sleep(10) # 模拟10秒的耗时工作
print(f"用户{user_id}的报表生成完成!")
# 这里可以把报表存到数据库/发邮件
# 前端请求接口:启动后台线程,立刻返回响应
@app.route("/generate_report/<int:user_id>")
def start_report(user_id):
# 创建线程,启动后台任务
thread = threading.Thread(
target=generate_report,
args=(user_id,),
daemon=True # 设为守护线程:主程序退出时自动结束
)
thread.start()
# 立刻给用户返回“任务已启动”
return jsonify({"msg": f"报表生成任务已启动,请稍后查看!"})
if __name__ == "__main__":
app.run(debug=True)
用户访问
/generate_report/123
,会立刻收到“任务已启动”的响应,不用干等10秒——同时后台线程在悄悄生成报表,体验直接拉满!
四、线程的“坑”:共享数据要锁
4. 线程的“雷区”:共享数据要加“锁” 🔒
关键字
:线程安全、锁(Lock)、数据竞争 内容详解: 多个线程如果
同时改同一个变量
,会出现“数据竞争”(结果混乱)。比如两个线程同时给计数器+1:
# 错误示例:无锁的共享数据
counter = 0
def add_counter():
global counter
for _ in range(100000):
counter += 1 # 这行代码不是“原子操作”,多线程会出错
# 启动两个线程改计数器
thread1 = threading.Thread(target=add_counter)
thread2 = threading.Thread(target=add_counter)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("最终计数器值:", counter) # 预期200000,但实际可能是150000左右
解决办法是用
threading.Lock()
加锁——同一时间只允许一个线程改共享数据:
# 正确示例:加锁保护共享数据
counter = 0
lock = threading.Lock() # 创建锁对象
def add_counter_safe():
global counter
for _ in range(100000):
lock.acquire() # 加锁:其他线程会等
counter += 1
lock.release() # 解锁:其他线程可以进来
# 再试一次,结果正确
thread1 = threading.Thread(target=add_counter_safe)
thread2 = threading.Thread(target=add_counter_safe)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("最终计数器值:", counter) # 输出200000,正确!
这就是
线程安全
——用锁保证共享数据的操作“原子性”(要么做完,要么没做,不会中间被打断)。
五、Flask的“线程帮手”:更优雅的后台任务
5. 进阶:Flask+线程的“优雅玩法” 🚀
关键字
:Flask扩展、Celery、轻量级替代 内容详解: 简单线程适合“轻量级后台任务”,但复杂场景(比如任务队列、失败重试)需要更专业的工具:
- 🥕 Celery:Python的分布式任务队列——把耗时任务丢到队列里,由专门的“ worker 线程”处理,支持重试、定时任务等;
- ✨ 轻量级替代:如果不想搞复杂的Celery,可以用
Flask-APScheduler做定时任务,或threading.Timer做延迟任务。
比如用
threading.Timer
做“5秒后给用户发通知”:
def send_notification(user_id):
print(f"给用户{user_id}发送通知:报表已生成!")
@app.route("/schedule_notify/<int:user_id>")
def schedule_notify(user_id):
# 5秒后执行send_notification
timer = threading.Timer(5, send_notification, args=(user_id,))
timer.start()
return "5秒后会给你发通知!"
这比手动管理线程更灵活~
六、线程的适用场景:啥时候用?
6. 划重点:线程的“适用&不适用” 🎯
关键字
:IO密集型任务、CPU密集型任务、GIL锁 内容详解: Python的线程受
GIL锁
(全局解释器锁)限制:
- ✅ 适用场景:IO密集型任务(比如查数据库、下载文件、等待网络响应)——线程会在“等待IO”时让出CPU,让其他线程干活,能提升效率;
- ❌ 不适用场景:CPU密集型任务(比如复杂计算)——GIL锁会让多线程变成“伪并行”,效率不如多进程。
所以Web应用里的“等待数据库/接口”用线程很合适,而“大量数学计算”建议用多进程~
要不要我帮你整理一份
Python线程的核心语法&Flask实战代码清单
?