python关于多线程的GIL问题,以及CPU分配核数的问题

本文深入探讨了Python中的全局解释器锁(GIL)机制,解释了GIL如何影响多线程程序在多核CPU上的执行效率,并提供了几种规避GIL限制的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于Python中,多线程的问题详细描述:

        在Python中,其实对于多线程的运行方案并不完美,纯属的Python多线程运行时,只能实现并发执行,对于现在的多核CPU来说,有点浪费CPU资源,但在其他的语言中,并没有这个问题。

    这一切都是由于时代的原因,在上个世纪80年代,由于硬件的发展,当时的电脑只是单核CPU,并没有今天的多核CPU。发明Python语言的龟叔,为了实现单核多任务的操作,提出了一个相当精彩的概念,就是GIL。

    GIL解决了单核CPU多任务的问题,但随着硬件的发展,同样也暴露出了GIL的缺点,即不能实现在多核CPU的今天,多线程的多任务并行执行。

为了探讨GIL的实现,先简单简绍一下多任务的概念:

    对于多任务,无论是多进程或者多线程,在具体的CPU执行阶段是按线程去分配CPU执行的,因为在CPU执行阶段会将进程分为线程执行每一个进程可以细分为一个主线程和n个子线程,在真正执行的时候,是由线程去争抢CPU的时间片, 即CPU分配资源和调度的基本单位是线程。通过时间片轮流机制实现多任务的执行效果。具体的时间片轮流机制可以查阅CPU的制造商的相关网站,每家的厂商对于时间片轮流机制的算法不尽相同,但都实现了同一个功能,即让CPU不停的切换多个线程,并让每个线程执行一个或者多个时间片(即是该线程竞争到的时间片,如果在该线程执行时遇到I/O操作,CPU会释放该线程资源,转而执行其他竞争到CPU时间片的线程)。

GIL的出现与现在出现的问题:

    GIL(全局解释器锁),该概念提出了在同一个进程下的多线程的任务中,保证同一时刻只能一个线程使用CPU,其他线程处于锁定状态,即其他线程不能去竞争CPU资源,或者说其他线程去竞争CPU资源,即使竞争到也会被CPU调度释放。GIL概念提出:在同一进程下的多线程执行时,只有拥有GIL锁的一个线程能够在CPU中进行运算执行,如果该线程在时间片用完时,或者在执行时遇到I/O操作,会释放GIL锁 ,转而将锁发放给其他竞争到CPU资源的线程拥有。

    因此,在哪个单核年代,龟叔的GIL概念算是完美了解决单核多任务的想法。但在计算机飞速发展的今天,随着多核CPU的出现,GIL反而成为了Python的弱点,即单进程多任务不能实现多任务的并行执行,降低了CPU的使用效率。即同一时间只能有一个线程执行。因此只能执行并发的方式,浪费了CPU的较高性能。


GIL是由解释器创建的。GIL本身也有执行的时间的规定和释放GIL锁资源的条件,是由龟叔在编写解释器时,,根据计算机性能去计算添加的,因此属于人为添加的。但在现在大多数的多任务(是指其他语言)是由编程语言编写,由CPU硬件去决定和分配资源去执行代码,而GIL是人为的添加CPU的执行。因此可以这么方便的解释Python的多任务,Python的多任务在进入执行时,不是像其他语言的是以线程进行CPU内核分配的,而是以进程为单位去进行CPU时间片资源竞争的。

以下是进程执行死循环时,竞争CPU一个核心资源的情况:



以下是一个进程下的线程,以进程为单位去竞争CPU资源,线程之间分配该线程竞争到的资源:

    看到现象好像是多个核心在同时执行,实际是只有一个核心在执行。但是由于CPU切换的资源的速度快到无法想象,因此你的眼睛欺骗了你。



多进程的执行情况



如何解决gil问题:

    1.更换cpython为jpython(不建议)

    2.使用多进程完成多线程的任务

    3.在使用多线程可以使用c语言去实现  参考:http://blog.youkuaiyun.com/cheryl_77/article/details/77160206

Gil全局解释器锁延伸扩展:


作用 : 保证同一时刻只有一个线程能使用到cup
解释 : 当我们使用多线程的时候,在一个进程中只有一个GIL锁,那么这多个线程中谁拿到GIL谁就可以
使用cpu(ps:多个进程有多个Gil锁)


问题1: 什么时候会释放Gil锁,
答 :  1 遇到像 i/o操作这种 会有时间空闲情况 造成cpu闲置的情况会释放Gil
  2 会有一个专门ticks进行计数 一旦ticks数值达到100 这个时候释放Gil锁 线程之间开始竞争Gil锁(说明:
ticks这个数值可以进行设置来延长或者缩减获得Gil锁的线程使用cpu的时间)

问题2: 互斥锁和Gil锁的关系

Gil锁  : 保证同一时刻只有一个线程能使用到cup
互斥锁 : 多线程时,保证修改共享数据时有序的修改,不会产生数据修改混乱


首先假设只有一个进程,这个进程中有两个线程 Thread1,Thread2, 要修改共享的数据date, 并且有互斥锁

执行以下步骤

(1)多线程运行,假设Thread1获得GIL可以使用cpu,这时Thread1获得 互斥锁lock,Thread1可以改date数据(但并
没有开始修改数据)

(2)Thread1线程在修改date数据前发生了 i/o操作 或者 ticks计数满100 (注意就是没有运行到修改data数据),这个
时候 Thread1 让出了Gil,Gil锁可以被竞争

(3) Thread1 和 Thread2 开始竞争 Gil (注意:如果Thread1是因为 i/o 阻塞 让出的Gil Thread2必定拿到Gil,如果
Thread1是因为ticks计数满100让出Gil 这个时候 Thread1 和 Thread2 公平竞争)

(4)假设 Thread2正好获得了GIL, 运行代码去修改共享数据date,由于Thread1有互斥锁lock,所以Thread2无法更改共享数据
date,这时Thread2让出Gil锁 , GIL锁再次发生竞争 


(5)假设Thread1又抢到GIL,由于其有互斥锁Lock所以其可以继续修改共享数据data,当Thread1修改完数据释放互斥锁lock,
Thread2在获得GIL与lock后才可对data进行修改

以上描述了 互斥锁和Gil锁的 一个关系

### Python多线程编程中获取CPU的方法 在Python中,可以通过多种方式获取当前系统的CPU核心量。以下是几种常见的方法: #### 方法一:使用 `multiprocessing` 模块 `multiprocessing` 是Python标准库的一部分,提供了用于跨平台并行处理的功能。其中的 `cpu_count()` 函可以直接返回系统中的逻辑CPU核心。 ```python import multiprocessing cores = multiprocessing.cpu_count() print(f"可用 CPU 核心:{cores}") ``` 这种方法简单易用,适合大多场景[^1]。 #### 方法二:通过环境变量获取 某些情况下,可能需要手动设置或读取环境变量来控制程序使用的CPU核心。例如,在科学计算领域常用的NumPy、SciPy等库会依赖OpenMP或多线程BLAS库(如Intel MKL),此时可以设置环境变量 `OMP_NUM_THREADS` 和 `MKL_NUM_THREADS` 来指定线程。 ```bash export OMP_NUM_THREADS=$(nproc) export MKL_NUM_THREADS=$(nproc) ``` 在脚本内部也可以动态读取这些环境变量: ```python import os omp_threads = int(os.getenv('OMP_NUM_THREADS', multiprocessing.cpu_count())) mkl_threads = int(os.getenv('MKL_NUM_THREADS', multiprocessing.cpu_count())) print(f"OMP_NUM_THREADS: {omp_threads}, MKL_NUM_THREADS: {mkl_threads}") ``` 这种方式特别适用于高性能计算环境中对资源的精细管理[^1]。 #### 方法三:借助第三方库 `psutil` 如果需要更详细的硬件信息,可以考虑安装并使用 `psutil` 库。它不仅能够提供CPU核心,还能区分物理核心和逻辑核心。 ```python import psutil physical_cores = psutil.cpu_count(logical=False) # 只统计物理核心 logical_cores = psutil.cpu_count(logical=True) # 统计所有逻辑核心 print(f"物理核心:{physical_cores},逻辑核心:{logical_cores}") ``` 注意,`psutil` 并不在Python的标准库中,需单独安装 (`pip install psutil`)。但对于复杂需求来说,这是一个非常强大的工具[^2]。 --- ### 关于Python多线程CPU的关系 尽管可以从技术层面轻松获得CPU核心目,但由于 **全局解释器锁 (Global Interpreter Lock, GIL)** 的存在,Python多线程并不适合作为提高CPU密集型任务性能的主要手段。具体而言,GIL允许同一时刻只有一个原生线程执行Python字节码,这使得即使有多个CPU核心也无法实现真正的并行运算[^3]。 然而,对于I/O密集型任务(比如等待网络响应或者磁盘操作完成的任务),多线程仍然有效果,因为在这种情况下线程会在等待期间释放GIL给其他线程运行的机会。 为了充分利用多处理器的能力,推荐采用以下策略之一: - 对于CPU密集型任务,改用基于多进程的技术,例如 `concurrent.futures.ProcessPoolExecutor` 或者直接使用 `multiprocessing` 模块; - 利用支持多线程且不受GIL影响的扩展库,像Cython编译后的代码片段、Numba JIT加速函等。 --- ### 示例代码展示 下面是一个简单的例子,演示如何根据CPU核心启动相应量的工作进程来进行批量据处理: ```python from concurrent.futures import ProcessPoolExecutor import multiprocessing def cpu_bound_task(x): """模拟一个耗时的CPU密集型任务""" return sum(i * i for i in range(10_000)) if __name__ == "__main__": cores = multiprocessing.cpu_count() with ProcessPoolExecutor(max_workers=cores) as executor: inputs = list(range(cores)) results = list(executor.map(cpu_bound_task, inputs)) print("Results:", results[:5]) # 打印部分结果验证正确性 ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值