Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 298 – 锁定的缓冲区接口

作者:
Thomas Heller <theller at python.net>
状态:
已撤回
类型:
标准跟踪
创建日期:
2002年7月26日
Python 版本:
2.3
发布历史:
2002年7月30日,2002年8月1日

目录

摘要

本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 标志。

acquirelockedreadbufferprocacquirelockedwritebufferproc 函数在成功时返回内存块的字节大小,并在成功时填充传入的 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 设置为内存块的字节长度。如果失败,或者对象未实现锁定缓冲区接口,则它们返回 -1 并设置异常。

后一个函数不返回任何内容,并且不会失败。

向后兼容性

如果实现此提案,PyBufferProcs 结构的大小会发生变化,但可以使用类型的 tp_flags 槽来确定是否存在附加字段。

参考实现

已将实现作为 https://bugs.python.org/issue652857 上传到 SourceForge 补丁管理器。

附加说明/评论

Python 字符串、unicode 字符串、mmap 对象和数组对象将公开锁定缓冲区接口。

mmap 和数组对象在缓冲区活动时实际上会进入锁定状态,字符串和 unicode 对象不需要此功能。不允许调整锁定的数组对象的大小,否则会引发异常。关闭锁定的 mmap 对象是错误还是只会推迟到锁定计数达到零是一个实现细节。

Guido 建议

但我仍然非常担心,如果大多数内置类型(例如字符串、字节)不实现释放功能,那么扩展很容易在忘记释放缓冲区的情况下看起来工作正常。

我建议至少某些内置类型使用计数器实现获取/释放功能,并断言在对象删除时计数器为零——如果断言失败,则有人在未释放对象的情况下对对象的引用进行了 DECREF。(规则应该是,在您获取对象时,您必须拥有对该对象的引用。)

对于字符串来说,这可能不切实际,因为字符串对象必须增加 4 字节来保存计数器;但新的字节对象(PEP 296)可以轻松实现计数器,数组对象也可以——这样就有足够的机会测试协议的正确使用。

社区反馈

Greg Ewing 怀疑是否根本需要锁定缓冲区接口,他认为如果每次使用时都(重新)获取指针,则可以使用普通缓冲区接口。这似乎很危险,因为即使是看起来无害的 Python API 调用,例如 Py_DECREF() 也可能触发任意 Python 代码的执行。

此提案的第一个版本没有 release 函数,但事实证明这会过于严格:mmap 和数组对象将无法实现它,因为 mmap 对象如果未锁定可以随时关闭,而数组对象可以调整或重新分配缓冲区。

这个PEP可能会被拒绝,因为除了作者之外没有人需要它。

参考资料


来源: https://github.com/python/peps/blob/main/peps/pep-0298.rst

最后修改: 2025-02-01 08:59:27 GMT