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 標誌。
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 設定為記憶體塊的位元組長度。如果失敗,或者物件未實現鎖定緩衝區介面,則它們返回 -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