在 Python C Extension 中使用来自 PyObjects 的数据而不持有 GIL

using data from PyObjects in Python C Extension without holding the GIL

在我的 Python C 扩展中,我正在对可迭代的字符串执行操作。因此,在第一步中,我调用 PySequence_Fast 将其转换为列表,然后遍历元素。对于每个字符串,我使用 PyUnicode_DATA 然后使用一些标准比较字符串。所以我只从 PyObjects 读取,但从不修改它们。

现在我想并行处理列表,这需要我释放 GIL。但是我不知道这对我的用例有什么影响。以下是我目前的想法:

  1. 我仍然可以使用那些 APIs,因为它们只是宏,直接从 PyObjects 读取而不修改它们。

  2. 我必须事先使用 APIs 并存储一个结构数组,其中包含字符串的 kindlengthdata pointer

  3. 我必须事先使用 API,并且必须将字符串的副本存储在数组中

情况 1 的性能和内存效率最高。然而,据说,如果没有获得 GIL,则不允许对 Python 对象执行(这是否包括读取访问权限)或使用 Python/C API 函数。

情况 2 将是下一个最有效的,因为至少我不必复制所有字符串。然而,当 GIL 被释放时不允许我从 Python 对象中读取时,我想知道我是否会被允许使用指向 PyObject 内部数据的指针。

情况 3 需要我复制所有字符串。在我的例子中,这可能会使多线程解决方案比顺序解决方案慢。

我希望有人能帮助我了解在 GIL 发布期间我可以做什么。

我认为官方的回答是你不应该使用方法 1 而应该使用方法 2 和 3。虽然它现在可能有效,但可能 将来会改变并且休息。如果您想支持 PyPy 的 C-API 包装器(它很可能在内部使用 Python 的不同表示)之类的东西,这一点尤其重要。有 increasing moves to try to hide implementation details 你可能会被抓到。

实际上,我认为方法 1 可以很好地工作,前提是您只使用没有错误检查的宏形式 - GIL 主要是关于停止将 Python 对象置于未定义状态的同时写入,而您不是这样做。我要稍微小心一点的是,如果您曾经拥有(不推荐使用的)“非规范”unicode 对象 - 看起来像 PyUnicode_READY 的“macro-y”的东西可能会导致它们被修改为规范状态。同样,要特别警惕 C-API.

的替代(非 CPython)实现

要考虑的一种替代方法是改用 buffer protocol。虽然我找不到在文档中明确说明的内容,但想法是 PyObject_GetBufferPyBuffer_Release 需要 GIL,但 reading/writing 不需要缓冲区。这里我有两个子建议:

  • 你能否拥有一个像 Numpy 数组这样的单一对象,将所有字符串公开为缓冲区?
  • 您还可以从 unicode 对象(作为 utf-8 C 字符串)获取缓冲区 - 要做的事情是使用 GIL 创建所有缓冲区,在没有 GIL 的情况下进行并行处理,然后释放它们他们与 GIL。这样做的开销可能效率低下。这基本上是方法 2 的“官方”版本。

简而言之,你可能会侥幸逃脱,但如果它坏了,我怀疑向 Python 提交的错误报告是否会受到欢迎(因为它在技术上是错误的)