赞
踩
Python 是一种流行的高级编程语言,以其简单、易用和快速开发而著称。然而,Python 的垃圾回收机制依赖于全局解释器锁(GIL: Global Interpreter Lock),这可能会造成一些限制。本文将探讨 Python 中指针的各个方面,尤其是 GIL 对内存管理、多线程和 CPU 利用率的影响。此外,本文还将提供具体示例来说明其局限性和解决方法。
公众号: 滑翔的纸飞机
Python 使用垃圾回收器来自动管理内存。垃圾回收器通过检测和删除程序不再使用的对象来释放内存。不过,垃圾回收器需要依赖全局解释器锁 (GIL) 才能正常工作。GIL 是一种防止多个线程同时执行 Python 字节码的机制。GIL 是必要的,因为 Python 的内存管理不是线程安全的,这意味着两个线程不能同时访问相同的内存位置,否则会有损坏数据的风险。
GIL 对内存管理有一些影响。例如,它可以防止垃圾回收器在多个线程中同时运行。因此,在垃圾回收器运行之前,不再使用的对象所占用的内存不会被清除。这可能会导致内存泄漏并降低性能。
下面的例子可以说明这种限制:
""" @Time:2023/11/7 23:10 @Describe: """ import threading class MyClass: def __init__(self): self.my_list = [] def add_value(self, value): self.my_list.append(value) my_object = MyClass() def add_values(): for i in range(10000000): my_object.add_value(i) thread_1 = threading.Thread(target=add_values) thread_2 = threading.Thread(target=add_values) thread_1.start() thread_2.start() thread_1.join() thread_2.join() print(len(my_object.my_list))
在本例中:
(1)定义了一个类 MyClass,并包含一个列表属性 my_list;
(2)add_values()
函数将 1000 万个值添加到 my_list;
(3)最后,创建该类的一个实例,然后创建两个调用该函数的线程,最后打印长度;
然而,由于 GIL 的存在,两个线程无法并行运行,程序执行时间并不会减少太多。此外,垃圾回收器可能不会在线程执行期间运行,这意味着添加值所使用的内存可能不会被清除。这可能会导致内存泄漏并降低性能。
全局解释器锁 (GIL) 也会影响 Python 中的多线程。GIL 的工作原理是为每个变量加锁,并维护一个使用计数器。如果一个线程想访问一个已被另一个线程使用的变量,它必须等到第一个线程释放了该变量。因此,一次只能有一个线程执行 Python 字节码。
这一限制可能会对严重依赖 CPU 操作的多线程程序产生影响。 例如,如果一个程序有两个执行复杂计算的线程,GIL将阻止它们并行运行,并且该程序将无法从使用多个CPU核心中受益。
下面的例子可以说明这种限制:
""" @Time:2023/11/7 23:19 @Describe: """ import threading def fib(n): if n <= 1: return n else: return fib(n - 1) + fib(n - 2) def compute_fib(): for i in range(30): print(fib(i)) thread_1 = threading.Thread(target=compute_fib) thread_2 = threading.Thread(target=compute_fib) thread_1.start() thread_2.start() thread_1.join() thread_2.join()
在本例中:
(1)定义了一个计算第 n 个斐波那契数的 fib
函数。
(2)定义了一个 compute_fib
函数,通过调用 fib
函数来计算前 30 个斐波那契数。
(3)同时,创建两个调用 compute_fib
函数的线程,然后启动它们。
(4)最后,我们使用 join
方法等待线程结束。
然而,由于GIL的原因,两个线程无法并行执行Python字节码。 因此,程序不会从使用多个 CPU 核心中受益,并且执行时间与使用单线程相同。 为了提高CPU 多核利用率,我们可以使用多进程(multiprocessing)。 多进程模块允许我们创建多个并行运行的进程,每个进程都有自己的解释器和内存空间。 这意味着每个进程都可以使用自己的CPU核心来执行Python字节码,进程GIL相互不影响。
下面的示例说明了如何使用多进程模块计算斐波那契数列:
import multiprocessing
def fib(n):
if n <= 1:
return n
else:
return fib(n-1) + fib(n-2)
def compute_fib(start, end):
for i in range(start, end):
print(fib(i))
if name == 'main':
with multiprocessing.Pool(processes=2) as pool:
pool.starmap(compute_fib, [(0, 15), (15, 30)])
在本例中:
(1)定义了一个计算第 n 个斐波那契数字的 fib
函数;
(2)定义了一个 compute_fib
函数,该函数通过调用 fib
函数来计算一系列数字的斐波那契数列。我们使用 multiprocessing.Pool
方法创建一个由两个进程组成的进程池,然后使用 starmap
方法对两个数字范围(0 至 15 和 15 至 30)执行 compute_fib
函数。starmap
方法会将数字范围分配给两个进程,每个进程会计算其分配范围内的斐波那契数列。最后打印出结果;
通过使用多进程模块,我们可以利用多个 CPU 内核并行执行 Python 字节码,而不会受到 GIL 的影响。这可以大大提高 CPU 的性能。
Python中的线程需要先获取GIL锁才能继续运行,每一个进程仅有一个GIL,线程在获取到GIL之后执行100字节码或者遇到IO中断时才会释放GIL,这样在CPU密集的任务中,即使有多个CPU,多线程也是不能够利用多个CPU来提高速率,甚至可能会因为竞争GIL导致速率慢于单线程。所以对于CPU密集任务往往使用多进程,IO密集任务使用多线程。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。