PEP 298 – 锁定缓冲区接口
- 作者:
- Thomas Heller <theller at python.net>
- 状态:
- 已撤回
- 类型:
- 标准跟踪
- 创建:
- 2002-07-26
- Python 版本:
- 2.3
- 历史记录:
- 2002-07-30, 2002-08-01
摘要
本 PEP 提出对缓冲区接口的扩展,称为“锁定缓冲区接口”。
锁定缓冲区接口避免了在 Python 版本 2.2 及更早版本中定义的“旧”缓冲区接口 [1] 的缺陷,并具有以下语义
- 检索到的指针的生命周期明确定义并由客户端控制。
- 缓冲区大小以“size_t”数据类型返回,这允许在
sizeof(int) != sizeof(void *)
的平台上访问大型缓冲区。
(Guido 评论:第二点听起来像是我们也可以对“旧”缓冲区接口进行的更改,如果我们引入另一个不属于默认标志的标志位。)
规范
锁定缓冲区接口公开新的函数,这些函数返回任何选择实现此接口的 Python 对象的内部内存块的大小和指针。
从对象检索缓冲区会将该对象置于锁定状态,在此期间,缓冲区不得被释放、调整大小或重新分配。
如果不再使用缓冲区,则必须通过调用锁定缓冲区接口中的另一个函数来释放缓冲区,从而解锁对象。如果对象在其生命周期内从未调整大小或重新分配缓冲区,则此函数可能为 NULL。如果未调用此函数(如果它不等于 NULL),则为编程错误,可能会导致意外结果。
锁定缓冲区接口省略了“旧”缓冲区接口中存在的内存段模型 - 只能公开单个内存块。
可以访问内存块,而无需持有全局解释器锁。
实现
在 Include/object.h 中定义一个新标志
/* PyBufferProcs contains bf_acquirelockedreadbuffer,
bf_acquirelockedwritebuffer, and bf_releaselockedbuffer */
#define Py_TPFLAGS_HAVE_LOCKEDBUFFER (1L<<15)
此标志将包含在 Py_TPFLAGS_DEFAULT
中
#define Py_TPFLAGS_DEFAULT ( \
....
Py_TPFLAGS_HAVE_LOCKEDBUFFER | \
....
0)
通过 Include/object.h 中的新字段扩展 PyBufferProcs
结构
typedef size_t (*acquirelockedreadbufferproc)(PyObject *,
const void **);
typedef size_t (*acquirelockedwritebufferproc)(PyObject *,
void **);
typedef void (*releaselockedbufferproc)(PyObject *);
typedef struct {
getreadbufferproc bf_getreadbuffer;
getwritebufferproc bf_getwritebuffer;
getsegcountproc bf_getsegcount;
getcharbufferproc bf_getcharbuffer;
/* locked buffer interface functions */
acquirelockedreadbufferproc bf_acquirelockedreadbuffer;
acquirelockedwritebufferproc bf_acquirelockedwritebuffer;
releaselockedbufferproc bf_releaselockedbuffer;
} PyBufferProcs;
如果对象类型的 Py_TPFLAGS_HAVE_LOCKEDBUFFER
标志设置,则存在新字段。
Py_TPFLAGS_HAVE_LOCKEDBUFFER
标志意味着 Py_TPFLAGS_HAVE_GETCHARBUFFER
标志。
acquirelockedreadbufferproc
和 acquirelockedwritebufferproc
函数在成功时返回内存块的大小(以字节为单位),并在成功时填充传入的 void * 指针。如果这些函数失败 - 要么是因为发生错误,要么是因为没有公开内存块 - 它们必须将 void * 指针设置为 NULL 并引发异常。在这些情况下,返回值未定义,不应使用。
如果对这些函数的调用成功,最终必须通过调用 releaselockedbufferproc
来释放缓冲区,并将原始对象作为参数提供。releaselockedbufferproc
不会失败。对于实际维护内部锁定计数的对象,如果 releaselockedbufferproc
函数被调用过于频繁,导致锁定计数为负,将是致命错误。
与“旧”缓冲区接口类似,任何这些函数都可以设置为 NULL,但强烈建议实现 releaselockedbufferproc
函数(即使它什么也不做),如果任何 acquireread
/writelockedbufferproc
函数已实现,以阻止扩展编写者检查 NULL 值而不调用它。
这些函数不应直接调用,它们通过在 Include/abstract.h 中声明的便利函数调用。
int PyObject_AcquireLockedReadBuffer(PyObject *obj,
const void **buffer,
size_t *buffer_len);
int PyObject_AcquireLockedWriteBuffer(PyObject *obj,
void **buffer,
size_t *buffer_len);
void PyObject_ReleaseLockedBuffer(PyObject *obj);
前两个函数在成功时返回 0,将 buffer 设置为内存位置,并将 buffer_len 设置为内存块的长度(以字节为单位)。在失败时,或如果 obj 未实现锁定缓冲区接口,它们将返回 -1 并设置异常。
最后一个函数不返回值,并且不会失败。
向后兼容性
如果实施了本提案,PyBufferProcs
结构的大小将发生变化,但类型的 tp_flags
插槽可用于确定是否存在附加字段。
参考实现
已将实现上传到 SourceForge 修补程序管理器,作为 https://bugs.python.org/issue652857。
其他说明/评论
Python 字符串、Unicode 字符串、mmap 对象和数组对象将公开锁定缓冲区接口。
mmap 和数组对象实际上将在缓冲区处于活动状态时进入锁定状态,字符串和 Unicode 对象不需要这样做。不允许调整锁定数组对象的大小,这将引发异常。关闭锁定 mmap 对象是错误还是仅延迟到锁定计数达到零是实现细节。
Guido 建议
但我仍然非常担心,如果大多数内置类型(例如字符串、字节)没有实现释放功能,那么扩展很容易看起来有效,而忘记释放缓冲区。我建议至少一些内置类型使用计数器实现获取/释放功能,并断言在删除对象时计数器为零 - 如果断言失败,则有人在未释放对象的情况下对其引用进行了 DECREF。(规则应该是,在获取对象时,必须拥有该对象的引用。)
对于字符串,这可能不切实际,因为字符串对象必须增长 4 个字节才能保存计数器;但是新的字节对象 (PEP 296) 可以轻松实现计数器,数组对象也可以 - 这样一来,将有充分的机会测试协议的正确使用。
社区反馈
Greg Ewing 怀疑是否需要锁定缓冲区接口,他认为如果每次使用指针时都(重新)获取指针,则可以使用普通缓冲区接口。这似乎很危险,因为即使是对 Python API 的看似无辜的调用(如 Py_DECREF()
)也可能会触发任意 Python 代码的执行。
本提案的第一个版本没有释放函数,但事实证明这将过于严格:mmap 和数组对象将无法实现它,因为 mmap 对象可以在任何时间关闭(如果未锁定),并且数组对象可以调整大小或重新分配缓冲区。
本 PEP 可能会被拒绝,因为除了作者之外,没有人需要它。
参考文献
版权
本文件已进入公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0298.rst
最后修改时间: 2023-09-09 17:39:29 GMT