并发编程机制
计算机的任务主要分为两种,一种是计算型密集任务,另一种则是IO密集型任务
在同步IO编程中,由于CPU处理任务计算的速度(通常为纳秒级)远高于内存执行IO任务的速度(磁盘IO通常为毫秒级,网络IO甚至可能达到秒级),所以会遇到IO阻塞引发的执行效率低的问题。具体表现为:
- 阻塞机制:
- 当线程执行到同步IO操作(如文件读写、数据库查询、网络请求)时,会立即被阻塞
- 操作系统会将线程状态从运行态转为等待态
- 线程会被移出CPU调度队列,直到IO操作完成
- 性能影响示例:
- 假设一个Web服务器处理请求需要:
- 5μs的CPU计算时间
- 50ms的数据库查询(同步IO)
- 此时CPU利用率仅为0.01%(5μs/(50ms+5μs))
- 典型应用场景:
- 文件批量处理程序
- 传统的关系型数据库操作
- 基于阻塞Socket的网络服务
- 问题本质:
- CPU时钟周期与IO延迟的巨大差异(通常相差5-6个数量级)
- 线程上下文切换带来的额外开销
- 资源浪费在等待而非实际计算上
- 量化影响:
- 单线程处理能力公式:
吞吐量 = 1/(CPU时间 + IO时间) - 在多核系统中,同步IO会导致:
- 大量线程处于等待状态
- 线程切换开销增加
- 内存消耗上升(每个线程需要独立栈空间)
python多线程并发缺点
- 进程的创建和销毁代价非常高
- 需要开辟更多的虚拟空间
- 多进程之间上下文的切换时间长
- 需要考虑多进程之间的同步问题
python 多线程并发缺点
- 每一个线程都包含一个内核调用栈(kenerl stack)和CPU寄存器上下文表
- 共享同一个进程空间会设计同步问题
- 受限于GIL,在python进程中只允许一个线程处于允许状态,多线程无法充分利用CPU多核
- 受OS调度管制,线程是抢占式多任务并发的
针对于受限于GIL,在python进程中只允许一个线程处于允许状态,多线程无法充分利用CPU多核
Python 的 GIL(全局解释器锁)导致多线程无法利用 CPU 多核的问题,至今没有被「彻底解决」(核心解释器 CPython 仍保留 GIL),但官方和社区提供了多种「绕开 GIL」的方案,能满足绝大多数多核利用的场景
核心结论:GIL 仍未被移除(针对 CPython)
GIL 是 CPython 解释器的核心锁,其设计初衷是简化内存管理(避免多线程竞争解释器状态),但代价是同一进程内的多线程无法并行执行 Python 字节码 ------无论 CPU 有多少核,一个 Python 进程中始终只有一个线程在执行计算型任务。
截至 Python 3.13(最新稳定版),CPython 仍未移除 GIL:
- 2023 年 Python 指导委员会曾讨论「GIL 移除计划(PEP 703)」,但该计划仍处于实验阶段,且即使落地,也会是「可选移除」(默认保留 GIL 以兼容现有代码);
- 其他 Python 解释器(如 PyPy、Jython、IronPython)无 GIL,但生态兼容性(如第三方库支持)远不如 CPython,无法作为主流方案。
绕开 GIL 利用多核的方案
多进程
利用 multiprocessing 或 concurrent.futures.ProcessPoolExecutor,创建多个独立的 Python 进程(每个进程有自己的 GIL),直接利用多核。
- 原理:每个进程独立占用一个 CPU 核,进程间通过管道 / 队列通信,不受 GIL 限制;
- 适用场景:CPU 密集型任务(如数据计算、机器学习、加密解密)。
python
from multiprocessing import Pool
import time
# 计算型任务(模拟耗时操作)
def cpu_bound_task(n):
total = 0
for i in range(n):
total += i **2
return total
if __name__ == "__main__":
start = time.time()
# 创建 4 个进程(对应 4 核 CPU)
with Pool(processes=4) as pool:
# 并行执行任务
results = pool.map(cpu_bound_task, [10**7]*4)
end = time.time()
print(f"耗时:{end - start:.2f} 秒") #
多线程 + 异步(仅适用于 IO 密集型任务)
GIL 仅在「执行 Python 字节码」时生效,对于 IO 密集型任务(如网络请求、文件读写、数据库操作),线程会频繁进入「等待状态」(释放 GIL),此时多线程 / 异步能充分利用多核的「等待时间」,间接提升效率。
- 原理:IO 等待时线程释放 GIL,其他线程可抢占执行,核心是「利用等待时间」而非「并行计算」;
- 适用场景:Web 服务、爬虫、数据库批量操作等。
python
import asyncio
import aiohttp
import time
async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
async def main():
start = time.time()
urls = ["https://www.baidu.com"] * 100
# 异步并行请求(利用 IO 等待时间)
tasks = [asyncio.create_task(fetch_url(url)) for url in urls]
await asyncio.gather(*tasks)
end = time.time()
print(f"耗时:{end - start:.2f} 秒")
if __name__ == "__main__":
asyncio.run(main())
将计算逻辑移到 C 扩展 / 第三方库
很多高性能库(如 NumPy、Pandas、TensorFlow)的核心计算逻辑是用 C/C++ 实现的,执行这些代码时会释放 GIL,因此即使在单线程中调用,也能利用多核。
- 原理:C 扩展代码执行时,解释器释放 GIL,操作系统可调度其他线程 / 进程利用多核;
python
import numpy as np
import time
# NumPy 矩阵乘法(C 实现,释放 GIL)
a = np.random.rand(10000, 10000)
b = np.random.rand(10000, 10000)
start = time.time()
c = np.dot(a, b) # 自动利用多核
end = time.time()
print(f"耗时:{end - start:.2f} 秒")