解释Python扩展的多线程

4 投票
1 回答
1705 浏览
提问于 2025-04-15 22:39

Python 解释器有一个叫做全局解释器锁(GIL)的东西,我理解在多线程环境下,扩展需要获取这个锁。不过,Boost.Python 的使用说明提到,扩展函数在结束时必须释放这个锁,然后再重新获取。

我不想随便猜测,所以我想知道在以下情况下,GIL 的锁定模式应该是什么样的:

  1. 扩展是从 Python 调用的(假设是在一个 Python 线程中运行)。
  2. 扩展的后台线程又调用了 Py_* 函数。

最后一个问题是,为什么链接的文档说应该释放 GIL 然后再重新获取?

1 个回答

6

每当Python在解释字节码时,当前正在运行的线程会持有全局解释器锁(GIL)。在这个时候,其他的Python线程都不能运行,直到它们能够获取到GIL。

当解释器调用本地代码时,这段代码有两个选择关于GIL:

  1. 它可以什么都不做。
  2. 它可以在工作时释放GIL,然后在返回到Python之前重新获取它。

如果本地代码频繁调用Python的运行时,那么最好选择第一个选项:除非你持有GIL,否则你不能安全地调用Python的运行时(有一些例外,比如在你没有GIL时调用获取GIL的函数)。

如果本地代码进行大量与Python无关的工作,那么你可以选择第二个选项:一旦你释放了GIL,Python就可以安排其他的Python线程运行,这样你就能实现一些并行处理。如果你不释放GIL,那么在你的Boost代码运行时,其他Python线程就无法执行:这就是文档中建议你释放并重新获取GIL的原因。

如果你选择这种方式,那么你必须小心,在释放GIL之前或重新获取GIL之后,才能访问Py_*函数。这可能意味着你需要对数据进行本地复制,因为在释放GIL时,不能安全地访问Python的数据类型,比如列表或字典的元素。

如果你的Boost代码在释放GIL时需要回调Python,那么你需要先获取GIL,进行调用,然后再释放GIL。尽量避免从不是由Python创建的线程中进行这样的调用,因为这些线程需要额外的工作才能获取GIL。

撰写回答