Python

동기화 프리미티브

C-API는 기본적인 상호 배제(mutual exclusion) 록을 제공합니다.

type PyMutex

상호 배제(mutual exclusion) 록입니다. PyMutex 은 잠금 해제 상태를 나타내기 위해 0으로 초기화되어야 합니다. 예:

PyMutex mutex = {0};

PyMutex 인스턴스는 복사하거나 이동해서는 안 됩니다. PyMutex 의 내용과 주소 모두 의미가 있으며, 고정된 쓰기 가능한 메모리 위치에 있어야 합니다.

참고

PyMutex 는 현재 1바이트를 차지하지만, 크기는 불안정한 것으로 간주해야 합니다. 향후 파이썬 버전에서 폐지 예고 기간 없이 크기가 변경될 수 있습니다.

Added in version 3.13.

void PyMutex_Lock(PyMutex *m)
Thread safety: Atomic.

뮤텍스 m 을 잠급니다. 다른 스레드가 이미 이를 잠근 경우, 호출한 스레드는 뮤텍스가 해제될 때까지 대기합니다. 차단된 동안 스레드는 존재하는 경우 스레드 상태 를 일시적으로 분리합니다.

Added in version 3.13.

void PyMutex_Unlock(PyMutex *m)
Thread safety: Atomic.

뮤텍스 m 을 해제합니다. 뮤텍스가 잠겨 있어야 하며, 그렇지 않으면 함수는 치명적 오류를 발생시킵니다.

Added in version 3.13.

int PyMutex_IsLocked(PyMutex *m)
Thread safety: Atomic.

뮤텍스 m 이 현재 잠겨 있으면 0이 아닌 값을 반환하고, 그렇지 않으면 0을 반환합니다.

참고

이 함수는 단지 확인(assertion) 및 디버깅 용도로만 설계되었으며, 체크 직후에 록 상태가 바뀔 수 있으므로 동시성 제어를 위한 결정에 사용해서는 안 됩니다.

Added in version 3.14.

파이썬 크리티컬 섹션(critical section) API

The critical section API provides a deadlock avoidance layer on top of per-object locks for free-threaded CPython. They are intended to replace reliance on the global interpreter lock, and are no-ops in versions of Python with the global interpreter lock.

Critical sections are intended to be used for custom types implemented in C-API extensions. They should generally not be used with built-in types like list and dict because their public C-APIs already use critical sections internally, with the notable exception of PyDict_Next(), which requires critical section to be acquired externally.

크리티컬 섹션은 활성화된 크리티컬 섹션을 암시적으로 중단함으로써 데드록을 방지하므로, PyMutex 와 같은 전통적인 록이 제공하는 독점적 접근 권한을 제공하지 않습니다. 크리티컬 섹션이 시작될 때 해당 객체의 개체별 록이 확보됩니다. 만약 크리티컬 섹션 내부에서 실행되는 코드가 C-API 함수를 호출하면, 해당 크리티컬 섹션을 중단하여 개체별 록을 해제할 수 있으며, 이를 통해 다른 스레드가 동일한 객체에 대한 개체별 록을 획득할 수 있습니다.

파이썬 객체 대신 PyMutex 포인터를 수용하는 변형(variant)도 제공됩니다. PyObject 가 없는 상황—예를 들어, PyObject 를 확장하거나 래핑하지 않으면서 데드락을 유발할 가능성이 있는 방식으로 C API를 호출해야 하는 C 타입과 작업하는 경우—에 크리티컬 섹션을 시작하려면 이러한 변형을 사용하십시오.

참고

동시에 두 객체를 잠가야 하는 작업은 반드시 Py_BEGIN_CRITICAL_SECTION2 를 사용해야 합니다. 내부 크리티컬 섹션이 외부 크리티컬 섹션을 중단시킬 수 있으므로, 중첩된 크리티컬 섹션을 사용하여 동시에 둘 이상의 객체를 잠그는 것은 불가능 합니다. 이 API는 세 개 이상의 객체를 동시에 잠그는 방법을 제공하지 않습니다.

사용 예시:

static PyObject *
set_field(MyObject *self, PyObject *value)
{
   Py_BEGIN_CRITICAL_SECTION(self);
   Py_SETREF(self->field, Py_XNewRef(value));
   Py_END_CRITICAL_SECTION();
   Py_RETURN_NONE;
}

위 예제에서 Py_SETREFPy_DECREF 를 호출하며, 이는 객체의 해제 함수를 통해 임의의 코드를 실행할 수 있습니다. 크리티컬 섹션 API는 파이널라이저에 의해 트리거된 코드가 블록되어 PyEval_SaveThread() 를 호출하는 경우, 런타임이 일시적으로 크리티컬 섹션을 중단할 수 있게 함으로써 재진입성 및 잠금 순서로 인한 잠재적인 데드락을 방지합니다.

Py_BEGIN_CRITICAL_SECTION(op)
…의 일부 안정 ABI 버전 3.15 이후로.

객체 op 에 대한 개별 객체 잠금을 획득하고 크리티컬 섹션을 시작합니다.

free-threaded 빌드 및 Stable ABI 를 위해 빌드할 때, 이 매크로는 다음과 같이 확장됩니다:

{
    PyCriticalSection _py_cs;
    PyCriticalSection_Begin(&_py_cs, (PyObject*)(op))

기본 빌드에서 이 매크로는 다음과 같이 확장됩니다. {.

Added in version 3.13.

Py_BEGIN_CRITICAL_SECTION_MUTEX(m)

뮤텍스 m 을 잠그고 크리티컬 섹션을 시작합니다.

free-threaded 빌드에서 이 매크로는 다음과 같이 확장됩니다:

{
     PyCriticalSection _py_cs;
     PyCriticalSection_BeginMutex(&_py_cs, m)

Py_BEGIN_CRITICAL_SECTION 과 달리, 이 매크로의 인자에 대한 캐스팅이 없으며 반드시 PyMutex 포인터여야 함에 유의하십시오.

기본 빌드에서 이 매크로는 다음과 같이 확장됩니다. {.

Added in version 3.14.

Py_END_CRITICAL_SECTION()
…의 일부 안정 ABI 버전 3.15 이후로.

크리티컬 섹션을 종료하고 개별 객체 잠금을 해제합니다.

free-threaded 빌드 및 Stable ABI 를 위해 빌드할 때, 이 매크로는 다음과 같이 확장됩니다:

    PyCriticalSection_End(&_py_cs);
}

기본 빌드에서 이 매크로는 다음과 같이 확장됩니다. }.

Added in version 3.13.

Py_BEGIN_CRITICAL_SECTION2(a, b)
…의 일부 안정 ABI 버전 3.15 이후로.

객체 ab 에 대한 개별 객체 잠금을 획득하고 크리티컬 섹션을 시작합니다. 잠금 순서로 인한 데드락을 방지하기 위해 두 잠금은 일관된 순서(낮은 주소부터)로 획득됩니다.

free-threaded 빌드에서 이 매크로는 다음과 같이 확장됩니다:

{
    PyCriticalSection2 _py_cs2;
    PyCriticalSection2_Begin(&_py_cs2, (PyObject*)(a), (PyObject*)(b))

기본 빌드에서 이 매크로는 다음과 같이 확장됩니다. {.

Added in version 3.13.

Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2)

뮤텍스 m1m2 를 잠그고 크리티컬 섹션을 시작합니다.

free-threaded 빌드 및 Stable ABI 를 위해 빌드할 때, 이 매크로는 다음과 같이 확장됩니다:

{
     PyCriticalSection2 _py_cs2;
     PyCriticalSection2_BeginMutex(&_py_cs2, m1, m2)

Py_BEGIN_CRITICAL_SECTION2 와 달리, 이 매크로의 인자에 대한 캐스팅이 없으며 반드시 PyMutex 포인터여야 함에 유의하십시오.

기본 빌드에서 이 매크로는 다음과 같이 확장됩니다. {.

Added in version 3.14.

Py_END_CRITICAL_SECTION2()
…의 일부 안정 ABI 버전 3.15 이후로.

크리티컬 섹션을 종료하고 개별 객체 잠금을 해제합니다.

free-threaded 빌드 및 Stable ABI 를 위해 빌드할 때, 이 매크로는 다음과 같이 확장됩니다:

    PyCriticalSection2_End(&_py_cs2);
}

기본 빌드에서 이 매크로는 다음과 같이 확장됩니다. }.

Added in version 3.13.

저수준 크리티컬 섹션 API

C 매크로를 사용할 수 없는 경우를 위해 다음 함수와 구조체가 노출됩니다.

void PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op)
void PyCriticalSection_End(PyCriticalSection *c)
void PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b)
void PyCriticalSection2_End(PyCriticalSection2 *c);
…의 일부 안정 ABI 버전 3.15 이후로.

이 기능은 단지 이 섹션 앞부분에 나열된 의 매크로 확장과 동일한 경우에만 사용되어야 합니다.

CPython의 비-머지된(non-) free-threaded 빌드에서 이 함수들은 아무것도 수행하지 않습니다.

Added in version 3.13.

type PyCriticalSection
type PyCriticalSection2
…의 일부 안정 ABI (모든 멤버 포함) 버전 3.15 이후로.

이 기능은 단지 이 섹션 앞부분에 나열된 의 매크로 확장과 동일한 경우에만 사용되어야 합니다. 구조체의 내용은 비공개이며 향후 파이썬 버전에서 그 의미가 변경될 수 있음에 유의하십시오.

Added in version 3.13.

void PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m);
void PyCriticalSection2_BeginMutex(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2);

이 기능은 단지 이 섹션 앞부분에 나열된 의 매크로 확장과 동일한 경우에만 사용되어야 합니다.

CPython의 비-머지된(non-) free-threaded 빌드에서 이 함수들은 아무것도 수행하지 않습니다.

Added in version 3.14.

레거시 잠금 API

이 API들은 PyMutex 가 도입된 파이썬 3.13부터 구식(obsolete)입니다.

버전 3.15에서 변경: 이 API들은 이제 PyMutex 를 단순하게 래핑한 것입니다.

type PyThread_type_lock

상호 배제(mutual exclusion) 록에 대한 포인터입니다.

type PyLockStatus

타임아웃을 사용하여 록을 획득한 결과입니다.

enumerator PY_LOCK_FAILURE

록 획득에 실패했습니다.

enumerator PY_LOCK_ACQUIRED

록이 성공적으로 획득되었습니다.

enumerator PY_LOCK_INTR

시그널에 의해 록 획득이 중단되었습니다.

PyThread_type_lock PyThread_allocate_lock(void)
…의 일부 안정 ABI.

새 록을 할당합니다.

성공 시 이 함수는 록을 반환하고, 실패 시 예외를 설정하지 않고 0 을 반환합니다.

호출자가 첨부된 스레드 상태 를 보유할 필요가 없습니다.

버전 3.15에서 변경: 이 함수는 이제 항상 PyMutex 를 사용합니다. 이전 버전에서는 운영체제에서 제공하는 록을 사용했습니다.

void PyThread_free_lock(PyThread_type_lock lock)
…의 일부 안정 ABI.

lock 을 파괴합니다. 이 함수를 호출할 때 해당 록은 어떤 스레드도 보유하고 있어서는 안 됩니다.

호출자가 첨부된 스레드 상태 를 보유할 필요가 없습니다.

PyLockStatus PyThread_acquire_lock_timed(PyThread_type_lock lock, long long microseconds, int intr_flag)
…의 일부 안정 ABI.

타임아웃을 설정하여 lock 을 획득합니다.

이 함수는 록을 획득하기 위해 microseconds 마이크로초 동안 대기합니다. 타임아웃이 만료되면 이 함수는 PY_LOCK_FAILURE 를 반환합니다. microseconds-1 인 경우, 록이 해제될 때까지 무기한으로 기다립니다.

intr_flag1 인 경우, 록 획득이 시그널에 의해 중단될 수 있으며, 이 경우 함수는 PY_LOCK_INTR 를 반환합니다. 중단 시 호출자는 일반적으로 파이썬 코드에 예외를 전파하기 위해 Py_MakePendingCalls() 를 호출해야 합니다.

록을 성공적으로 획득한 경우 이 함수는 PY_LOCK_ACQUIRED 를 반환합니다.

호출자가 첨부된 스레드 상태 를 보유할 필요가 없습니다.

int PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
…의 일부 안정 ABI.

lock 을 획득합니다.

waitflag1 이고 다른 스레드가 현재 록을 보유하고 있는 경우, 이 함수는 록을 획득할 수 있을 때까지 대기하며 항상 1 을 반환합니다.

waitflag0 이고 다른 스레드가 록을 보유하고 있는 경우, 이 함수는 기다리지 않고 대신 0 을 반환합니다. 어떤 다른 스레드도 록을 보유하고 있지 않은 경우, 이 함수는 록을 획득하고 1 을 반환합니다.

PyThread_acquire_lock_timed() 와 달리, 록 획득은 시그널에 의해 중단될 수 없습니다.

호출자가 첨부된 스레드 상태 를 보유할 필요가 없습니다.

int PyThread_release_lock(PyThread_type_lock lock)
…의 일부 안정 ABI.

lock 을 해제합니다. lock 이 보유되지 않은 상태에서 이 함수를 호출하면 치명적인 오류가 발생합니다.

호출자가 첨부된 스레드 상태 를 보유할 필요가 없습니다.