Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 298 – 잠금 버퍼 인터페이스

Author:
Thomas Heller <theller at python.net>
Status:
Withdrawn
Type:
Standards Track
Created:
26-Jul-2002
Python-Version:
2.3
Post-History:
30-Jul-2002, 01-Aug-2002

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 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로 설정할 수 있지만, acquireread/writelockedbufferproc 함수 중 하나라도 구현되어 있다면 releaselockedbufferproc 함수를 구현하는 것이 강력히 권장됩니다(아무 작업도 하지 않더라도). 이는 확장 작성자가 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 문자열, 유니코드 문자열, mmap 객체, array 객체는 잠긴 버퍼 인터페이스를 노출하게 됩니다.

mmap 및 array 객체는 버퍼가 활성 상태인 동안 실제로 잠금 상태에 들어가지만, 문자열과 유니코드 객체에는 이것이 필요하지 않습니다. 잠긴 array 객체의 크기를 조정하는 것은 허용되지 않으며 예외를 발생시킵니다. 잠긴 mmap 객체를 닫는 것이 오류인지, 아니면 잠금 카운트가 0에 도달할 때까지 지연될 뿐인지는 구현 세부 사항입니다.

Guido의 권장 사항

하지만 저는 여전히, 대부분의 내장 타입(예: 문자열, 바이트)이 해제 기능을 구현하지 않는다면 확장이 버퍼 해제를 잊은 채로도 동작하는 것처럼 보이기가 너무 쉽다는 점이 걱정됩니다.

저는 적어도 일부 내장 타입이 카운터를 사용해 획득/해제 기능을 구현하고, 객체가 삭제될 때 카운터가 0인지를 단언(assert)하도록 권장합니다 – 만약 이 단언이 실패한다면, 누군가 해제하지 않은 채로 객체에 대한 참조를 DECREF한 것입니다. (규칙은 객체를 획득하고 있는 동안에는 그 객체에 대한 참조를 소유하고 있어야 한다는 것이어야 합니다.)

문자열의 경우 문자열 객체가 카운터를 담기 위해 4바이트 더 커져야 하므로 실용적이지 않을 수 있지만, 새로운 bytes 객체(PEP 296)는 카운터를 쉽게 구현할 수 있고 array 객체도 마찬가지입니다 – 그렇게 하면 프로토콜의 올바른 사용을 테스트할 기회가 충분히 생길 것입니다.

커뮤니티 피드백

Greg Ewing은 잠긴 버퍼 인터페이스가 애초에 필요한지 의문을 제기하며, 포인터를 사용할 때마다 (다시) 가져오도록 하면 일반 버퍼 인터페이스를 사용할 수 있다고 생각합니다. 이는 위험해 보이는데, Py_DECREF()처럼 무해해 보이는 Python API 호출조차도 임의의 Python 코드 실행을 유발할 수 있기 때문입니다.

이 제안의 첫 번째 버전에는 release 함수가 없었지만, 이는 지나치게 제한적이었던 것으로 드러났습니다: mmap 객체는 잠기지 않은 경우 언제든지 닫힐 수 있고 array 객체는 버퍼의 크기를 조정하거나 재할당할 수 있기 때문에, mmap 객체와 array 객체는 이를 구현할 수 없었을 것입니다.

이 PEP는 작성자 외에는 아무도 필요로 하지 않기 때문에 아마 거부될 것입니다.

참고 문헌