Python 中的 GIL(全局解释器锁)

举报
赵KK日常技术记录 发表于 2023/07/28 11:34:55 2023/07/28
【摘要】 Python 中的 GIL(全局解释器锁)​ 简介在Python中,GIL是一个广为人知的概念,它影响了Python解释器的多线程执行。GIL(Global Interpreter Lock)是一种机制,它可以确保在同一时间只有一个线程在Python解释器中执行字节码。这意味着,尽管Python中有多线程的概念,但在实际执行过程中,同一时刻只有一个线程被允许执行。在本文中,我们将探讨Pyt...

Python 中的 GIL(全局解释器锁)

简介

在Python中,GIL是一个广为人知的概念,它影响了Python解释器的多线程执行。GIL(Global Interpreter Lock)是一种机制,它可以确保在同一时间只有一个线程在Python解释器中执行字节码。这意味着,尽管Python中有多线程的概念,但在实际执行过程中,同一时刻只有一个线程被允许执行。

在本文中,我们将探讨Python中的GIL是如何工作的,它对多线程编程的影响,以及一些绕过GIL的方法。

GIL的原因

GIL的存在是由于Python解释器的设计选择。Python解释器的设计目标之一是简单易用,并且能够提供良好的开发体验。为了实现这个目标,Python解释器使用了一个全局解释器锁(GIL),用于同步对Python对象的访问。

由于GIL的存在,Python解释器不能利用多核处理器的优势,因为即使在多线程环境下,所有的线程都需要竞争GIL才能执行字节码。

GIL的影响

GIL的存在对于CPU密集型的Python程序来说是一个负面影响,因为在多线程环境下,由于GIL的限制,无法利用多核处理器的优势。而对于I/O密集型的程序来说,GIL的影响相对较小,因为在进行I/O操作时,线程会主动释放GIL,让其他线程有机会执行。

下面我们通过一个简单的代码示例来说明GIL的影响:

import threading

def count_squares(n):
    sum_of_squares = 0
    for i in range(n):
        sum_of_squares += i * i
    print(sum_of_squares)

def main():
    n = 10000000
    # 创建两个线程
    thread1 = threading.Thread(target=count_squares, args=(n,))
    thread2 = threading.Thread(target=count_squares, args=(n,))
    # 启动线程
    thread1.start()
    thread2.start()
    # 等待线程结束
    thread1.join()
    thread2.join()

if __name__ == '__main__':
    main()

在上面的示例中,我们定义了一个函数count_squares,用于计算给定范围内的平方和。然后,我们创建了两个线程并分别调用count_squares函数进行计算。最后,我们等待两个线程执行完毕。

然而,不幸的是,由于GIL的存在,这两个线程并不能同时执行。实际上,它们将以交替的方式执行,因为每当一个线程获得GIL并开始执行时,另一个线程就会被阻塞。

绕过 GIL 的方法

尽管GIL对于某些类型的应用程序来说是个问题,但并不意味着不能通过一些方法来绕过它,从而实现更好的并发性能。

1. 使用多进程

通过使用多个进程而不是线程,可以绕过GIL。在Python中,可以使用multiprocessing模块来创建多个进程并进行并发执行。每个进程都会有自己的解释器进程,从而避免了GIL的限制。

下面是一个使用multiprocessing模块的示例:

import multiprocessing

def count_squares(n):
    sum_of_squares = 0
    for i in range(n):
        sum_of_squares += i * i
    print(sum_of_squares)

def main():
    n = 10000000
    # 创建两个进程
    process1 = multiprocessing.Process(target=count_squares, args=(n,))
    process2 = multiprocessing.Process(target=count_squares, args=(n,))
    # 启动进程
    process1.start()
    process2.start()
    # 等待进程结束
    process1.join()
    process2.join()

if __name__ == '__main__':
    main()

在上面的示例中,我们使用multiprocessing.Process函数创建了两个进程,并分别调用count_squares函数进行计算。每个进程都有自己的解释器进程,因此能够绕过GIL的限制进行并行执行。

2. 使用多线程执行I/O操作

如前所述,GIL对于I/O密集型的程序影响相对较小。因此,如果你的应用程序主要涉及到I/O操作,那么可以使用多线程来实现并发执行。

下面是一个简单的示例:

import threading
import requests

def download(url):
    response = requests.get(url)
    print(f"Downloaded {len(response.content)} bytes")

def main():
    urls = [
        "https://example.com",
        "https://google.com",
        "https://github.com"
    ]
    # 创建多个线程下载网页内容
    threads = []
    for url in urls:
        t = threading.Thread(target=download, args=(url,))
        threads.append(t)
        t.start()
    # 等待所有线程结束
    for t in threads:
        t.join()

if __name__ == '__main__':
    main()

在上面的示例中,我们使用多线程来并发下载网页内容。每个线程都会执行download函数来下载指定的URL,并在下载完成后打印下载的字节数。

由于下载操作涉及到网络I/O,因此线程会自动释放GIL,让其他线程有机会执行。因此,多线程可以在这种场景下提供一定的并发性能优势。

结论

GIL是Python解释器中的一个重要概念,它限制了多线程的并发执行。在CPU密集型的程序中,由于线程需要竞争GIL,因此无法利用多核处理器的优势。然而,在I/O密集型的程序中,GIL的影响相对较小,因为线程在进行I/O操作时会主动释放GIL。

要绕过GIL,可以使用多进程来实现并行执行,或者在I/O密集型的场景下使用多线程。通过合理的程序设计和选择适当的并发模型,可以最大程度地发挥Python的多线程编程的优势。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。