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 플래그를 함축합니다.

acquirelockedreadbufferproc 및 acquirelockedwritebufferproc 함수는 성공하면 메모리 블록의 크기를 바이트 단위로 반환하고, 전달받은 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 객체, 배열 객체가 잠긴 버퍼 인터페이스를 제공하게 됩니다.

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

Guido의 권고

하지만 대부분의 내장 타입(예: 문자열, bytes)이 해제 기능을 구현하지 않는다면, 확장 모듈이 버퍼 해제를 잊어버려도 제대로 동작하는 것처럼 보이기가 너무 쉽다는 점이 여전히 매우 우려됩니다.

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

문자열의 경우 카운터를 담기 위해 문자열 객체가 4바이트 늘어나야 하므로 이것이 비현실적일 수 있습니다. 하지만 새로운 bytes 객체(PEP 296)와 배열 객체는 카운터를 쉽게 구현할 수 있으므로, 그렇게 하면 프로토콜을 올바르게 사용하는지 시험해 볼 기회가 충분히 생깁니다.

커뮤니티 피드백

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

이 제안의 첫 번째 버전에는 해제 함수가 없었지만, 그렇게 하면 제약이 너무 크다는 것이 드러났습니다. mmap 객체는 잠겨 있지 않으면 언제든 닫힐 수 있고 배열 객체는 버퍼의 크기를 바꾸거나 버퍼를 재할당할 수 있으므로, mmap 객체와 배열 객체가 이 인터페이스를 구현할 수 없었을 것입니다.

저자 외에는 아무도 필요로 하지 않으므로 이 PEP는 아마 거부될 것입니다.

참고 문헌