在python中受限于GIL,进程中只允许一个线程处于允许状态,多线程无法充分利用CPU多核

并发编程机制

计算机的任务主要分为两种,一种是计算型密集任务,另一种则是IO密集型任务

在同步IO编程中,由于CPU处理任务计算的速度(通常为纳秒级)远高于内存执行IO任务的速度(磁盘IO通常为毫秒级,网络IO甚至可能达到秒级),所以会遇到IO阻塞引发的执行效率低的问题。具体表现为:

  1. 阻塞机制:
  • 当线程执行到同步IO操作(如文件读写、数据库查询、网络请求)时,会立即被阻塞
  • 操作系统会将线程状态从运行态转为等待态
  • 线程会被移出CPU调度队列,直到IO操作完成
  1. 性能影响示例:
  • 假设一个Web服务器处理请求需要:
    • 5μs的CPU计算时间
    • 50ms的数据库查询(同步IO)
  • 此时CPU利用率仅为0.01%(5μs/(50ms+5μs))
  1. 典型应用场景:
  • 文件批量处理程序
  • 传统的关系型数据库操作
  • 基于阻塞Socket的网络服务
  1. 问题本质:
  • CPU时钟周期与IO延迟的巨大差异(通常相差5-6个数量级)
  • 线程上下文切换带来的额外开销
  • 资源浪费在等待而非实际计算上
  1. 量化影响:
  • 单线程处理能力公式:
    吞吐量 = 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} 秒")
相关推荐
啦哈拉哈1 小时前
【Python】知识点零碎学习1
数据结构·python·算法
捧 花1 小时前
Go Web 开发流程
开发语言·后端·golang·restful·web·分层设计
多恩Stone1 小时前
【3DV 进阶-10】Trellis 中的表示 SLat 理解(1)
人工智能·python·算法·3d·aigc
南猿北者1 小时前
go语言基础语法
开发语言·后端·golang
CHANG_THE_WORLD1 小时前
Python容器转换与共有函数详解
网络·python·rpc
高洁011 小时前
循环神经网络讲解
人工智能·python·神经网络·机器学习·transformer
CC.GG1 小时前
【Qt】Qt初识
开发语言·qt
子午2 小时前
【中草药识别系统】Python+TensorFlow+Django+人工智能+深度学习+卷积神经网络算法
人工智能·python·深度学习
Sunsets_Red2 小时前
二项式定理
java·c++·python·算法·数学建模·c#