Python深入了解GIL锁
什么是GIL锁?
GIL全局解释器锁(Global Interpreter Lock),是Python解释器的一种机制。在Python多线程执行的时候,由于GIL锁的存在,同一时间只有一个线程能够执行,其他的线程只能在等待队列中等待。
GIL锁的存在意义
在解释Python代码的时候,Python会将解释器内存中的字节码编译成对底层处理器指令的解释器程序。由于Python具有便捷易用的GIL(Global Interpreter Lock)线程锁,这意味着在任何时候只能有一个线程从你的程序中运行Python代码。
这个锁的存在使得Python的多线程执行得以实现,但在某些情况下也可能会阻止Python的多线程执行。
Python实现线程的方式
Python实现线程的方式有两种:Thread
对象和对于多线程执行优化的Thread Pool
。
-
Thread
对象:主要用于短期的线程调用,例如APIs,即在你使用APIs访问外部资源的时候,Python将会使用线程调用,但是在执行你的应用程序的主体时依旧必须采用单线程执行方式。 -
Thread Pool
:因为它们复用现有的线程对象,所以与“Thread”对象相比,这种执行方法在处理长时间运行的操作(例如数据I/O)方面更加优越
GIL锁的优劣
优势:
-
GIL锁让对于Python 的多线程执行的管理变得更加简单而可行。
-
GIL锁可以实际上确保Python代码的安全性。
弊端:
-
在“密集计算”(CPU-bound)情况下,Python 使用线程模型可能会在运行时花费更多的时间。
-
在“io处理”(I/o-bound)情况下,Python使用线程时,多线程执行的效率可能会比单线程略快,但是使用100个线程通常比使用10个线程要慢得多!
示例说明
为了更好地理解Python GIL锁的机制,下面的示例说明将创建两个线程并测量它们在同时运行时的效率。
import threading
from datetime import datetime
def job(n):
print(f'{n} starts!')
for i in range(n):
pass
print(f'{n} ends!')
start_time = datetime.now()
# 创建两个线程
t1 = threading.Thread(target=job, args=(100000000,))
t2 = threading.Thread(target=job, args=(100000000,))
# 启动两个线程
t1.start()
t2.start()
# 等待两个线程结束
t1.join()
t2.join()
end_time = datetime.now()
print(f'Time cost: {end_time - start_time}')
运行结果如下:
1 starts!
2 starts!
1 ends!
2 ends!
Time cost: 0:00:04.104312
我们可以看到,尽管我们使用了两个线程,但是它们却是一个接一个地运行的。因为即使在运行多线程的情况下,Python的GIL锁仍然保证只有一个线程在执行。
现在我们再看一个I/O密集型的例子,使用Python的concurrent.futures
模块中的ThreadPoolExecutor
类实现多线程执行。以下代码从50个网站下载图标,并测量了每次请求的时间和总时间。
import requests
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
urls = [
'https://www.github.com/favicon.ico',
'https://www.python.org/favicon.ico',
'http://www.cnblogs.com/favicon.ico',
'http://c.biancheng.net/favicon.ico',
'https://www.baidu.com/favicon.ico',
'https://www.sina.com.cn/favicon.ico',
'http://www.tmall.com/favicon.ico',
'https://www.jd.com/favicon.ico',
'http://www.pku.edu.cn/favicon.ico',
'http://www.fudan.edu.cn/favicon.ico'
]
def download_one(url):
img = requests.get(url)
print(f'Download from {url}, and save it.')
return url
start_time = datetime.now()
with ThreadPoolExecutor(max_workers=5) as executor:
for url in urls:
executor.submit(download_one, url)
end_time = datetime.now()
print(f'Time cost: {end_time - start_time}')
运行结果如下:
Download from http://www.cnblogs.com/favicon.ico, and save it.
Download from https://www.baidu.com/favicon.ico, and save it.
Download from https://www.jd.com/favicon.ico, and save it.
Download from https://www.github.com/favicon.ico, and save it.
Download from http://c.biancheng.net/favicon.ico, and save it.
Download from http://www.tmall.com/favicon.ico, and save it.
Download from https://www.sina.com.cn/favicon.ico, and save it.
Download from https://www.python.org/favicon.ico, and save it.
Download from http://www.fudan.edu.cn/favicon.ico, and save it.
Download from http://www.pku.edu.cn/favicon.ico, and save it.
Time cost: 0:00:01.405767
我们可以看到,这个例子中多线程的效率比单线程快得多,原因是因为它是一个I/O密集型操作,而不是一个CPU密集型操作。在这种情况下,Python多线程执行的效率通常比单线程更高。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:python 深入了解GIL锁详细 - Python技术站