太棒了!欢迎来到 【跟着AI学Python】Python进阶:并发与并行!🎉今天我们将深入 Python 的三大并发模型:
💡 线程(Threading) → 轻量级,适合 I/O 密集型任务💡 进程(Multiprocessing) → 真正并行,适合 CPU 密集型任务💡 协程(Asyncio) → 单线程高并发,适合大量 I/O 操作(如网络请求)
🎯 今日目标
✅ 理解 GIL(全局解释器锁) 对线程的影响✅ 掌握 threading 模拟多用户并发访问✅ 使用 asyncio 实现非阻塞网络请求✅ 区分 并发 vs 并行
📘 一、核心概念速览
| | | | |
|---|
| 线程 | threading | | | |
| 进程 | multiprocessing | | | |
| 协程 | asyncio | | | |
🔑 GIL 是什么?CPython 的全局锁,确保同一时刻只有一个线程执行 Python 字节码 → 线程无法真正并行 CPU 计算
🔧 二、实践 1:用线程模拟多用户访问网站
场景:10 个用户同时登录银行系统
# concurrent_demo.pyimport threadingimport timeimport random# 模拟银行登录接口(I/O 操作:网络延迟)def login_user(user_id): print(f"👤 用户 {user_id} 开始登录...") # 模拟网络延迟(0.5~2秒) time.sleep(random.uniform(0.5, 2.0)) print(f"✅ 用户 {user_id} 登录成功!")# 同步方式(顺序执行)def sync_login(): start = time.time() for i in range(1, 6): login_user(i) print(f"⏱️ 同步耗时: {time.time() - start:.2f} 秒\n")# 多线程方式(并发执行)def thread_login(): start = time.time() threads = [] for i in range(1, 6): # 创建线程 t = threading.Thread(target=login_user, args=(i,)) threads.append(t) t.start() # 启动线程 # 等待所有线程完成 for t in threads: t.join() print(f"⏱️ 多线程耗时: {time.time() - start:.2f} 秒\n")if __name__ == "__main__": print("=== 同步登录 ===") sync_login() print("=== 多线程登录 ===") thread_login()
▶️ 输出示例
✅ 为什么快?虽然受 GIL 限制,但 I/O 等待期间会释放 GIL,其他线程可运行!
⚠️ 线程安全问题(重要!)
如果多个线程操作共享资源(如全局变量),需加锁:
# 不安全示例counter = 0def unsafe_increment(): global counter for _ in range(100000): counter += 1 # 非原子操作!# 安全示例(使用 Lock)lock = threading.Lock()def safe_increment(): global counter for _ in range(100000): with lock: # 自动 acquire/release counter += 1
🔒 银行系统启示:多用户同时取款时,必须用锁保护余额更新!
🔧 三、实践 2:用 asyncio 实现非阻塞网络请求
场景:同时请求多个 API(比线程更高效!)
# async_demo.pyimport asyncioimport aiohttp # 需安装: pip install aiohttpimport time# 异步函数:获取网页内容async def fetch_url(session, url): print(f"📡 请求: {url}") async with session.get(url) as response: text = await response.text() print(f"✅ 收到响应: {url} (长度: {len(text)})") return len(text)# 主协程:并发请求多个 URLasync def main_async(): urls = [ "https://baijiahao.baidu.com/s?id=1859560634159209643", "https://baijiahao.baidu.com/s?id=1859560634159209643", "https://baijiahao.baidu.com/s?id=1859560634159209643" ] async with aiohttp.ClientSession() as session: # 并发执行所有请求 tasks = [fetch_url(session, url) for url in urls] results = await asyncio.gather(*tasks) return results# 同步对比(使用 requests)import requestsdef main_sync(): urls = ["https://baijiahao.baidu.com/s?id=1859560634159209643"] * 3 results = [] for url in urls: print(f"📡 请求: {url}") resp = requests.get(url) print(f"✅ 收到响应: {url}") results.append(len(resp.text)) return resultsif __name__ == "__main__": # 测试异步 start = time.time() asyncio.run(main_async()) print(f"⏱️ Asyncio 耗时: {time.time() - start:.2f} 秒\n") # 测试同步 start = time.time() main_sync() print(f"⏱️ Requests 耗时: {time.time() - start:.2f} 秒")
▶️ 输出示例
✅ asyncio 优势:
- 适合 I/O 密集型高并发场景(如 Web 服务器)
🔍 四、关键对比:何时用哪种?
| |
|---|
| 下载 100 个网页 | |
| 处理 10 个用户上传文件 | |
| 计算 100 万次素数 | ✅ multiprocessing(绕过 GIL) |
| 实时聊天服务器 | |
| GUI 应用后台任务 | |
🧪 五、结合银行系统:并发取款模拟
# 模拟多用户同时取款(带锁保护)import threading, timeclass BankAccount: def __init__(self, balance=1000): self.balance = balance self.lock = threading.Lock() # 线程锁 def withdraw(self, amount, user): with self.lock: # 关键:保护共享资源 if self.balance >= amount: print(f"👤 {user} 取款 {amount}") self.balance -= amount time.sleep(0.1) # 模拟处理时间 print(f"✅ {user} 成功,余额: {self.balance}") else: print(f"❌ {user} 余额不足!")# 测试account = BankAccount(500)threads = []for i in range(3): t = threading.Thread(target=account.withdraw, args=(200, f"用户{i+1}")) threads.append(t) t.start()for t in threads: t.join()
🔒 没有锁的后果:可能出现“超取”(余额变成负数)或数据不一致!
📝 小结:并发编程心法
I/O 密集 → 用线程或 asyncioCPU 密集 → 用多进程高并发网络 → 首选 asyncio共享状态 → 必须加锁!
你的程序将因此:
🎉 恭喜完成并发与并行进阶!你已掌握让 Python 突破单线程限制的核心能力!
继续加油,你的代码正在变得越来越强大!⚡