Python中的GIL

目录

 

1.Python中的GIL

2.GIL对Python程序的性能影响

2.多进程


1.Python中的GIL

Python中的全局解释器锁GIL是一种互斥锁,仅允许一个线程持有Python解释器的控制权。这意味着在任何时间点只有一个线程可以处于执行状态。

对于执行单线程程序的开发人员而言,GIL的影响并不明显,但它可能是CPU绑定和多线程代码的性能瓶颈。由于即使在具有多个CPU内核的多线程体系结构中,GIL一次仅允许一个线程执行,因此GIL已被誉为Python的“臭名昭著”功能。

Python的内存管理使用了引用计数收集器,引用计数收集器是标准Python解释器的基础,并且始终在运行。它通过跟踪object程序运行时给定内存块(始终为Python )附加名称的次数来工作。引用计数变量需要保护,以防止出现两个线程同时增大或减小其值的竞争情况。如果发生这种情况,则可能导致从未释放的内存泄漏,或者更糟糕的是,在仍然存在对该对象的引用的情况下,错误地释放了内存。这可能会导致崩溃或Python程序中的其他“怪异”错误。Python使用全局解释器锁GIL解决该问题。

通过将添加到跨线程共享的所有数据结构中,以便不会被不一致地修改,可以保持此引用计数变量的安全。但是,将锁添加到每个对象或对象组意味着将存在多个锁,这可能会导致另一个问题-死锁(只有在有多个锁的情况下才会发生死锁)。另一个副作用是由于重复获取和释放锁而导致性能降低。

GIL是解释器本身的单一锁,它添加了一个规则,即任何Python字节码的执行都需要获取解释器锁。这样可以防止死锁(因为只有一个锁)并且不会带来太多的性能开销。但这实际上使所有受CPU约束的Python程序都是单线程的。

尽管解释器用于其他语言(例如Ruby),但GIL并不是解决此问题的唯一方法。某些语言通过使用引用计数以外的方法(例如垃圾回收)来避免对线程安全的内存管理使用GIL的要求。另一方面,这意味着这些语言通常必须通过添加其他性能提升功能(如JIT编译器)来弥补GIL的单线程性能优势的损失。

2.GIL对Python程序的性能影响

需要注意的是,受CPU限制的性能与受I / O限制的性能之间是有区别的。受CPU约束的程序是将CPU推到极限的程序。这包括进行数学计算的程序,例如矩阵乘法,搜索,图像处理等。

受I / O约束的程序是花费时间等待输入/输出的程序,这些程序可能来自用户,文件,数据库,网络等。受I / O约束的程序有时必须等待大量时间,直到它们进入由于源可能需要在输入/输出准备好之前进行自己的处理,因此可以从源那里获取他们需要的东西,例如,用户正在考虑要输入什么,或者在输入提示中运行数据库查询自己的过程。

#实现倒计时Python程序
#单线程
import time
from threading import Thread
num=50000000
def count_all(n):
	while n>0:
		n-=1
start=time.time()
count_all(num)
end=time.time()
print(end-start)
#2.9081664085388184 [Finished in 3.1s]


#两个并行线程
import time
from threading import Thread
num=50000000
def count_all(n):
	while n>0:
		n-=1
tr1=Thread(target=count_all,args=(num//2,))
tr2=Thread(target=count_all,args=(num//2,))
start=time.time()
tr1.start()
tr2.start()
tr1.join()
tr2.join()
end=time.time()
print(end-start)
#2.890165328979492 [Finished in 3.0s]

如上例所示,两个版本花费的时间几乎相同。在多线程版本中,GIL阻止CPU绑定的线程并行执行。

线程完全受CPU约束的程序(例如使用线程处理映像的程序),不仅会由于锁定而变为单线程,而且执行时间也会增加。与将其编写为完全单线程的方案相比,这种增加是锁增加的获取和释放开销的结果。

2.多进程

如何处理Python的GIL?

最流行的方法是使用多进程方法,其中您使用多个进程而不是线程。每个Python进程都有自己的Python解释器和内存空间,因此GIL不会成为问题。Python有一个multiprocessing模块,用多进程实现上文的倒计时程序:

#多进程
from multiprocessing import Pool
import time
num=50000000
def count_all(n):
	while n>0:
		n-=1
if __name__ == '__main__':
	pool=Pool(processes=2)
	start=time.time()
	pc1=pool.apply_async(count_all,[num//2])
	pc2=pool.apply_async(count_all,[num//2])
	pool.close()
	pool.join()
	end=time.time()
	print(end-start)
#1.6220927238464355 [Finished in 1.8s]

与多线程版本相比,性能提高了,但时间并没有减少到我们上面看到的一半,因为流程管理有其自己的开销。多个进程比多个线程重,因此请记住,这可能会成为扩展瓶颈。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值