실행 모델

실행 모델 (Execution Model)

이번 장에서는 Python 프로그램이 실제로 어떻게 실행되는지, 그 실행 모델을 하나씩 살펴볼게요. 프로그램이 코드 블록으로 어떻게 이루어져 있는지, 이름이 어떻게 묶이고 해석되는지, 그리고 예외가 어떻게 전파되고 복구되는지를 중심으로 설명해요. 코드가 '그냥 돌아가는' 게 아니라, 안에서 무슨 일이 일어나는지 이해하고 싶은 분들께 특히 도움이 되는 내용이에요.

출처: Python 언어 레퍼런스

4.1. 프로그램의 구조 (Structure of a program)

Python 프로그램은 코드 블록(code block)들로 이루어져 있어요. 코드 블록이란 Python 프로그램 텍스트 조각으로, 하나의 단위로 실행되는 것을 말해요. 어떤 것들이 코드 블록이냐 하면:

  • 모듈
  • 함수 본문
  • 클래스 정의
  • 대화형(interactive)으로 입력하는 각 명령
  • 스크립트 파일(인터프리터의 표준 입력으로 주어지거나 명령줄 인자로 지정된 파일)
  • 인터프리터 명령줄에 -c 옵션으로 지정된 스크립트 명령
  • 명령줄에서 -m 인자로 __main__ 모듈로써 최상위 스크립트로 실행되는 모듈
  • 내장 함수 eval()exec()에 전달되는 문자열 인자

즉, 어떤 형태로든 Python 인터프리터가 '한 덩어리'로 실행하는 텍스트라면 대부분 코드 블록이라고 보면 돼요.

이 코드 블록은 실행 프레임(execution frame) 안에서 실행돼요. 프레임에는 디버깅에 쓰이는 관리 정보가 들어 있고, 코드 블록 실행이 끝난 뒤 실행이 어디서 어떻게 계속될지도 결정해요.

4.2. 이름과 바인딩 (Naming and binding)

4.2.1. 이름의 바인딩 (Binding of names)

이름(name)은 객체를 가리켜요. 이름은 이름 바인딩 연산을 통해 도입돼요. 다음과 같은 구조들이 이름을 바인딩해요:

  • 함수의 형식 매개변수
  • 클래스 정의
  • 함수 정의
  • 대입 표현식(assignment expression)
  • 대입문에 등장하면서 식별자인 대상(target):
    • for 루프 헤더
    • with 문, except 절, except* 절에서 as 뒤에 오는 것, 그리고 구조적 패턴 매칭에서 as-패턴
    • 구조적 패턴 매칭의 캡처 패턴(capture pattern)
  • import
  • type
  • 타입 매개변수 목록(type parameter lists)

from ... import * 형태의 import 문은 임포트한 모듈에 정의된 모든 이름을 바인딩하는데, 밑줄로 시작하는 이름은 제외해요. 이 형태는 모듈 레벨에서만 사용할 수 있어요.

del 문에 등장하는 대상도 이 목적에서는 바인딩된 것으로 간주해요(실제 의미는 이름을 푸는 것이지만요).

각 대입문이나 import 문은 클래스 정의나 함수 정의로 만들어진 블록 안, 또는 모듈 레벨(최상위 코드 블록)에서 일어나요.

어떤 이름이 블록 안에서 바인딩되면, 그 이름은 그 블록의 지역 변수(local variable)가 돼요. 단 nonlocal이나 global로 선언된 경우는 제외해요. 이름이 모듈 레벨에서 바인딩되면 전역 변수(global variable)가 돼요(모듈 코드 블록의 변수는 지역이면서 동시에 전역이에요). 변수가 코드 블록 안에서 쓰이는데 그 블록에 정의돼 있지 않다면, 그건 자유 변수(free variable)예요.

프로그램 텍스트에서 이름이 등장할 때마다 그건 아래 이름 해석 규칙에 따라 확립된 그 이름의 _바인딩_을 가리켜요.

4.2.2. 이름의 해석 (Resolution of names)

스코프(scope)는 블록 안에서 이름의 가시성을 정의해요. 지역 변수가 어떤 블록에 정의되면, 그 스코프는 그 블록을 포함해요. 정의가 함수 블록에서 일어나면, 포함된 블록이 그 이름에 대해 다른 바인딩을 도입하지 않는 한, 스코프는 정의하는 블록 안에 포함된 모든 블록으로 확장돼요.

코드 블록에서 이름이 사용되면, 가장 가까이 둘러싸는 스코프를 사용해 해석돼요. 코드 블록이 볼 수 있는 이런 모든 스코프의 집합을 그 블록의 환경(environment)이라고 불러요.

이름이 전혀 찾아지지 않으면 NameError 예외가 발생해요. 현재 스코프가 함수 스코프인데, 이름이 사용되는 지점에서 아직 값에 바인딩되지 않은 지역 변수를 가리키면 UnboundLocalError 예외가 발생해요. UnboundLocalErrorNameError의 하위 클래스예요.

이름 바인딩 연산이 코드 블록 어디에 있든 등장하면, 블록 안에서 그 이름의 모든 사용은 현재 블록을 가리키는 참조로 취급돼요. 그 결과, 이름이 바인딩되기 전에 블록 안에서 사용되면 오류가 날 수 있어요. 이 규칙은 미묘해요. Python에는 선언(declaration)이 없고, 이름 바인딩 연산이 코드 블록 어디든 등장할 수 있어요. 코드 블록의 지역 변수는 블록 전체 텍스트를 훑어 바인딩 연산을 찾아내면 결정돼요. 예시는 UnboundLocalError에 관한 FAQ 항목을 참고해 주세요.

블록 안에 global 문이 있으면, 그 문에 지정된 이름의 모든 사용은 최상위 네임스페이스의 바인딩을 가리켜요. 이름은 전역 네임스페이스(코드 블록을 포함하는 모듈의 네임스페이스)와 builtins 네임스페이스(builtins 모듈의 네임스페이스)를 검색해 최상위 네임스페이스에서 해석돼요. 먼저 전역 네임스페이스를 검색하고, 거기 없으면 builtins 네임스페이스를 검색해요. builtins 네임스페이스에도 없으면 전역 네임스페이스에 새 변수가 생성돼요. global 문은 나열된 이름의 모든 사용 앞에 와야 해요.

global 문은 같은 블록의 이름 바인딩 연산과 동일한 스코프를 가져요. 자유 변수의 가장 가까운 둘러싸는 스코프에 global 문이 있으면, 그 자유 변수는 전역 변수로 취급돼요.

nonlocal 문은 해당 이름이 가장 가까운 둘러싸는 함수 스코프에 이전에 바인딩된 변수를 가리키게 해요. 주어진 이름이 어떤 둘러싸는 함수 스코프에도 없으면 컴파일 타임에 SyntaxError가 발생해요. 타입 매개변수는 nonlocal 문으로 다시 바인딩할 수 없어요.

모듈의 네임스페이스는 모듈이 처음 임포트될 때 자동으로 생성돼요. 스크립트의 메인 모듈은 항상 __main__이라고 불려요.

클래스 정의 블록과 exec()·eval()의 인자는 이름 해석 측면에서 특별해요. 클래스 정의는 이름을 사용하고 정의할 수 있는 실행 가능한 문(statement)이에요. 이런 참조는 일반적인 이름 해석 규칙을 따르되, 바인딩되지 않은 지역 변수를 전역 네임스페이스에서 찾는다는 한 가지 예외가 있어요. 클래스 정의의 네임스페이스는 클래스의 속성 사전이 돼요. 클래스 블록에 정의된 이름의 스코프는 클래스 블록으로 한정되고, 메서드의 코드 블록까지는 확장되지 않아요. 여기에는 컴프리헨션과 제너레이터 표현식도 포함되지만, 자신을 둘러싼 클래스 스코프에 접근할 수 있는 애너테이션 스코프는 포함되지 않아요. 이 때문에 다음 코드는 실패해요:

class A:
    a = 42
    b = list(a + i for i in range(10))

하지만 다음 코드는 성공해요:

class A:
    type Alias = Nested
    class Nested: pass

print(A.Alias.__value__)  # <type 'A.Nested'>

4.2.3. 애너테이션 스코프 (Annotation scopes)

애너테이션(annotation), 타입 매개변수 목록, type 문은 애너테이션 스코프를 도입해요. 애너테이션 스코프는 대부분 함수 스코프처럼 동작하지만, 아래에서 다룰 몇 가지 예외가 있어요.

애너테이션 스코프는 다음 맥락에서 사용돼요:

  • 함수 애너테이션
  • 변수 애너테이션
  • 제네릭 타입 별칭을 위한 타입 매개변수 목록
  • 제네릭 함수를 위한 타입 매개변수 목록. 제네릭 함수의 애너테이션은 애너테이션 스코프 안에서 실행되지만, 기본값(default)과 데코레이터는 그렇지 않아요.
  • 제네릭 클래스를 위한 타입 매개변수 목록. 제네릭 클래스의 기반 클래스와 키워드 인자는 애너테이션 스코프 안에서 실행되지만, 데코레이터는 그렇지 않아요.
  • 타입 매개변수의 경계(bounds), 제약(constraints), 기본값(지연 평가됨)
  • 타입 별칭의 값(지연 평가됨)

애너테이션 스코프는 다음 방식에서 함수 스코프와 달라요:

  • 애너테이션 스코프는 자신을 둘러싼 클래스 네임스페이스에 접근할 수 있어요. 애너테이션 스코프가 클래스 스코프 바로 안에 있거나, 클래스 스코프 바로 안에 있는 다른 애너테이션 스코프 안에 있으면, 애너테이션 스코프의 코드는 클래스 본문 안에서 직접 실행된 것처럼 클래스 스코프에 정의된 이름을 사용할 수 있어요. 이는 클래스 안에 정의된 일반 함수가 클래스 스코프에 정의된 이름에 접근할 수 없는 것과 대조적이에요.
  • 애너테이션 스코프의 표현식은 yield, yield from, await, 또는 := 표현식을 포함할 수 없어요. (이 표현식들은 애너테이션 스코프 안에 포함된 다른 스코프에서는 허용돼요.)
  • 애너테이션 스코프에 정의된 이름은 내부 스코프의 nonlocal 문으로 다시 바인딩할 수 없어요. 애너테이션 스코프 안에 나타날 수 있는 다른 구문 요소는 새 이름을 도입할 수 없으므로, 여기엔 타입 매개변수만 포함돼요.
  • 애너테이션 스코프는 내부 이름을 가지지만, 그 이름은 스코프 안에 정의된 객체의 정규화된 이름(qualified name)에는 반영되지 않아요. 대신 그런 객체의 __qualname__은 객체가 둘러싸는 스코프에 정의된 것처럼 돼요.

버전 3.12에서 추가됨: 애너테이션 스코프는 Python 3.12에서 PEP 695의 일부로 도입됐어요.

버전 3.13에서 변경됨: 애너테이션 스코프는 PEP 696에서 도입된 타입 매개변수 기본값에도 사용돼요.

버전 3.14에서 변경됨: 애너테이션 스코프는 이제 PEP 649와 PEP 749에서 명시된 대로 애너테이션에도 사용돼요.

4.2.4. 지연 평가 (Lazy evaluation)

대부분의 애너테이션 스코프는 지연 평가(lazily evaluated)돼요. 여기에는 애너테이션, type 문으로 만든 타입 별칭의 값, 타입 매개변수 구문으로 만든 타입 변수의 경계·제약·기본값이 포함돼요. 즉 그것들은 타입 별칭이나 타입 변수가 만들어질 때, 또는 애너테이션을 가진 객체가 만들어질 때 평가되지 않아요. 대신 필요할 때만 평가되는데, 예를 들어 타입 별칭의 __value__ 속성에 접근할 때 평가돼요.

예시:

>>> type Alias = 1/0
>>> Alias.__value__
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero
>>> def func[T: 1/0](): pass
>>> T = func.__type_params__[0]
>>> T.__bound__
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero

여기서 예외는 타입 별칭의 __value__ 속성이나 타입 변수의 __bound__ 속성에 접근할 때만 발생해요.

이 동작은 타입 별칭이나 타입 변수가 만들어질 때 아직 정의되지 않은 타입에 대한 참조에 특히 유용해요. 예를 들어 지연 평가는 상호 재귀적인 타입 별칭을 만들 수 있게 해 줘요:

from typing import Literal

type SimpleExpr = int | Parenthesized
type Parenthesized = tuple[Literal["("], Expr, Literal[")"]]
type Expr = SimpleExpr | tuple[SimpleExpr, Literal["+", "-"], Expr]

지연 평가된 값은 애너테이션 스코프에서 평가돼요. 즉 지연 평가된 값 안에 등장하는 이름은 바로 둘러싸는 스코프에서 사용된 것처럼 조회돼요.

버전 3.12에서 추가됨.

4.2.5. 내장(builtins)과 제한된 실행 (Builtins and restricted execution)

CPython 구현 세부 사항: 사용자는 __builtins__를 만지면 안 돼요. 그것은 엄밀히 구현 세부 사항이에요. builtins 네임스페이스의 값을 재정의하고 싶은 사용자는 builtins 모듈을 임포트해서 그 속성을 적절히 수정해야 해요.

코드 블록 실행과 연관된 builtins 네임스페이스는 실제로 그 전역 네임스페이스에서 __builtins__라는 이름을 찾아서 발견돼요. 이것은 딕셔너리이거나 모듈이어야 해요(모듈인 경우 모듈의 딕셔너리가 사용돼요). 기본적으로 __main__ 모듈에 있을 때는 __builtins__가 내장 모듈인 builtins이고, 다른 모듈에 있을 때는 __builtins__builtins 모듈 자체의 딕셔너리에 대한 별칭이에요.

4.2.6. 동적 기능과의 상호작용 (Interaction with dynamic features)

자유 변수의 이름 해석은 컴파일 타임이 아니라 런타임에 일어나요. 즉 다음 코드는 42를 출력해요:

i = 10
def f():
    print(i)
i = 42
f()

eval()exec() 함수는 이름을 해석할 때 전체 환경에 접근할 수 없어요. 이름은 호출자의 지역·전역 네임스페이스에서 해석될 수 있어요. 자유 변수는 가장 가까운 둘러싸는 네임스페이스가 아니라 전역 네임스페이스에서 해석돼요[1]. exec()eval() 함수는 전역·지역 네임스페이스를 재정의하는 선택적 인자를 가져요. 네임스페이스가 하나만 지정되면 그 하나가 둘 다에 사용돼요.

4.3. 예외 (Exceptions)

예외는 오류나 그 밖의 예외적인 상황을 처리하기 위해 코드 블록의 정상 제어 흐름을 벗어나는 수단이에요. 예외는 오류가 감지된 지점에서 발생(raised)하고, 둘러싸는 코드 블록이나 오류가 발생한 코드 블록을 직간접적으로 호출한 코드 블록이 처리(handled)할 수 있어요.

Python 인터프리터는 런타임 오류(예: 0으로 나누기)를 감지하면 예외를 발생시켜요. Python 프로그램은 raise 문으로 예외를 명시적으로 발생시킬 수도 있어요. 예외 핸들러는 tryexcept 문으로 지정해요. 그 문의 finally 절은 예외를 처리하지 않지만, 앞선 코드에서 예외가 발생했든 아니든 실행되는 정리(cleanup) 코드를 지정하는 데 쓸 수 있어요.

Python은 오류 처리를 위한 "종료" 모델(termination model)을 사용해요. 예외 핸들러는 무슨 일이 일어났는지 알아내고 바깥 레벨에서 실행을 계속할 수 있지만, 오류의 원인을 고쳐서 실패한 연산을 재시도할 수는 없어요(범인이 된 코드 조각을 처음부터 다시 진입하는 경우는 제외해요).

예외가 전혀 처리되지 않으면 인터프리터는 프로그램 실행을 종료하거나 대화형 메인 루프로 돌아가요. 두 경우 모두 스택 트레이스백을 출력하는데, 예외가 SystemExit인 경우는 예외예요.

예외는 클래스 인스턴스로 식별돼요. except 절은 인스턴스의 클래스에 따라 선택되는데, 인스턴스의 클래스나 그 비가상 기반 클래스(non-virtual base class)를 참조해야 해요. 인스턴스는 핸들러가 받을 수 있고, 예외 상황에 대한 추가 정보를 담을 수 있어요.

참고: 예외 메시지는 Python API의 일부가 아니에요. 그 내용은 경고 없이 Python 버전마다 바뀔 수 있고, 여러 버전의 인터프리터에서 실행되는 코드는 그 내용에 의존하면 안 돼요.

try 문과 raise 문에 대한 자세한 설명은 각각 'The try statement' 장과 'The raise statement' 장에서 다뤄요.

4.4. 런타임 컴포넌트 (Runtime Components)

4.4.1. 일반적인 컴퓨팅 모델 (General Computing Model)

Python의 실행 모델은 진공에서 동작하지 않아요. 호스트 머신 위에서, 그 호스트의 런타임 환경(운영체제가 있다면 그 OS 포함)을 통해 실행돼요. 프로그램이 실행될 때, 호스트에서 프로그램이 실행되는 방식을 개념적으로 보면 다음과 같아요:

host machine (호스트 머신)

process (전역 자원)

thread (기계 코드 실행)

각 프로세스는 호스트에서 실행 중인 프로그램 하나를 나타내요. 각 프로세스 자체를 그 프로그램의 '데이터 부분'이라고 생각해 보세요. 프로세스의 스레드를 프로그램의 '실행 부분'이라고 생각해 보세요. 이 구분은 개념적인 Python 런타임을 이해하는 데 중요해요.

데이터 부분으로서의 프로세스는 프로그램이 실행되는 실행 컨텍스트예요. 주로 호스트가 프로그램에 할당한 자원의 집합으로 이루어지는데, 메모리, 시그널, 파일 핸들, 소켓, 환경 변수를 포함해요.

프로세스는 서로 격리되고 독립적이에요(호스트도 마찬가지예요). 호스트는 프로세스가 할당된 자원에 접근하는 것을 관리하고, 프로세스 사이를 조정해요.

각 스레드는 프로그램의 기계 코드가 실제로 실행되는 것을 나타내며, 프로그램의 프로세스에 할당된 자원을 기준으로 실행돼요. 그 실행이 어떻게, 언제 일어나는지는 전적으로 호스트에게 달려 있어요.

Python의 관점에서 프로그램은 항상 정확히 하나의 스레드로 시작해요. 하지만 프로그램은 여러 동시 스레드에서 실행되도록 커질 수 있어요. 모든 호스트가 프로세스당 여러 스레드를 지원하지는 않지만, 대부분은 지원해요. 프로세스와 달리 한 프로세스 안의 스레드들은 서로 격리되지 않고 독립적이지 않아요. 구체적으로, 한 프로세스의 모든 스레드는 그 프로세스의 모든 자원을 공유해요.

스레드의 근본적인 요점은 각 스레드가 다른 스레드와 동시에 실제로 독립적으로 실행된다는 거예요. 그것은 개념적으로만 동시('동시성')일 수도, 물리적으로('병렬성')일 수도 있어요. 어느 쪽이든 스레드들은 사실상 동기화되지 않은 속도로 실행돼요.

참고: 이 동기화되지 않은 속도 때문에, 어떤 스레드에서 실행되는 코드에 대해 프로세스의 메모리 어느 것도 일관되게 유지된다고 보장되지 않아요. 따라서 멀티스레드 프로그램은 의도적으로 공유하는 자원에 대한 접근을 조정하는 데 신경을 써야 해요. 마찬가지로, 여러 스레드에서 다른 자원에 접근하지 않도록 철저히 주의해야 해요. 그렇지 않으면 동시에 실행되는 두 스레드가 우연히 어떤 공유 데이터의 사용을 서로 방해할 수 있기 때문이에요. 이 모든 것은 Python 프로그램과 Python 런타임 양쪽 모두에 해당돼요.

이 광범위하고 구조화되지 않은 요구사항의 비용은 스레드가 제공하는 생(raw) 동시성과 맞바꾸는 트레이드오프예요. 요구되는 규율의 대안은 일반적으로 비결정적(non-deterministic) 버그와 데이터 손상을 다루는 것이 돼요.

4.4.2. Python 런타임 모델 (Python Runtime Model)

같은 개념적 계층이 각 Python 프로그램에도 적용되는데, Python 고유의 데이터 계층이 몇 개 더 추가돼요:

host machine (호스트 머신)

process (전역 자원)

Python 전역 런타임 (state)

Python 인터프리터 (state)

thread (Python 바이트코드와 "C-API" 실행)

Python 스레드 state

개념적 수준에서, Python 프로그램이 시작되면 그 다이어그램과 똑같이 보이며, 각각 하나씩 있어요. 런타임은 여러 인터프리터를 포함하도록 커질 수 있고, 각 인터프리터는 여러 스레드 상태(thread state)를 포함하도록 커질 수 있어요.

참고: Python 구현이 반드시 런타임 계층을 구분해서, 심지어 구체적으로 구현하지는 않을 수 있어요. 유일한 예외는 구별된 계층이 사용자에게 직접 명시되거나 노출되는 곳인데, 예를 들어 threading 모듈을 통해서예요.

참고: 최초의 인터프리터는 보통 "메인" 인터프리터라고 불려요. CPython 같은 일부 Python 구현은 메인 인터프리터에 특별한 역할을 부여해요.

마찬가지로, 런타임이 초기화된 호스트 스레드는 "메인" 스레드로 알려져 있어요. 그것은 프로세스의 초기 스레드와 다를 수 있는데, 흔히 같긴 해요. 어떤 경우에는 "메인 스레드"가 더 구체적으로 초기 스레드 상태를 가리킬 수도 있어요. Python 런타임은 시그널 처리 같은 특정 책임을 메인 스레드에 부여할 수 있어요.

전체적으로 Python 런타임은 전역 런타임 상태, 인터프리터, 스레드 상태로 이루어져 있어요. 런타임은 특히 여러 호스트 스레드와 함께 사용될 때, 그 모든 상태가 수명 동안 일관되게 유지되도록 보장해요.

개념적 수준에서 전역 런타임은 단지 인터프리터들의 집합이에요. 그 인터프리터들은 서로 격리되고 독립적이지만, 일부 데이터나 다른 자원을 공유할 수는 있어요. 런타임은 이런 전역 자원을 안전하게 관리할 책임이 있어요. 이런 자원의 실제 성격과 관리는 구현에 특화돼 있어요. 결국 전역 런타임의 외부적 유용성은 인터프리터를 관리하는 것으로 한정돼요.

반면 "인터프리터"(interpreter)는 개념적으로 우리가 보통 "(완전한 기능의) Python 런타임"이라고 생각하는 것이에요. 호스트 스레드에서 실행되는 기계 코드가 Python 런타임과 상호작용할 때, 특정 인터프리터의 맥락에서 Python에 호출해요.

참고: 여기서 "인터프리터"라는 용어는 "바이트코드 인터프리터"와 같지 않아요. 바이트코드 인터프리터는 스레드에서 정기적으로 실행되며 컴파일된 Python 코드를 실행하는 것을 말해요.

이상적인 세계에서 "Python 런타임"은 우리가 현재 "인터프리터"라고 부르는 것을 가리킬 거예요. 하지만 최소한 1997년에 도입된 이후로 계속 "인터프리터"라고 불려 왔어요(CPython:a027efa5b).

각 인터프리터는 Python 런타임이 동작하기 위해 필요한, 프로세스 전역도 아니고 스레드 특화도 아닌 모든 상태를 완전히 캡슐화해요. 특히 인터프리터의 상태는 사용 사이에 지속돼요. sys.modules 같은 기초적인 데이터를 포함해요. 런타임은 같은 인터프리터를 사용하는 여러 스레드가 그걸 안전하게 공유하도록 보장해요.

Python 구현은 같은 프로세스에서 여러 인터프리터를 동시에 사용하는 것을 지원할 수 있어요. 그것들은 서로 독립적이고 격리돼 있어요. 예를 들어 각 인터프리터는 자신만의 sys.modules를 가져요.

스레드 특화 런타임 상태를 위해, 각 인터프리터는 전역 런타임이 인터프리터 집합을 포함하는 것과 같은 방식으로, 자신이 관리하는 스레드 상태 집합을 가져요. 필요한 만큼의 호스트 스레드에 대한 스레드 상태를 가질 수 있어요. 같은 호스트 스레드에 대한 여러 스레드 상태를 가질 수도 있는데, 흔하지는 않아요.

개념적으로 각 스레드 상태는 인터프리터가 하나의 호스트 스레드에서 동작하기 위해 필요한 모든 스레드 특화 런타임 데이터를 가져요. 스레드 상태에는 현재 발생한 예외와 스레드의 Python 호출 스택이 포함돼요. 다른 스레드 특화 자원도 포함할 수 있어요.

참고: "Python 스레드"라는 용어는 때로 스레드 상태를 가리킬 수 있지만, 보통은 threading 모듈을 사용해 만든 스레드를 뜻해요.

각 스레드 상태는 수명 동안 정확히 하나의 인터프리터와 정확히 하나의 호스트 스레드에 항상 결합돼 있어요. 그것은 그 스레드와 그 인터프리터에서만 사용돼요.

여러 스레드 상태가 같은 호스트 스레드에 결합될 수 있는데, 다른 인터프리터를 위해서든 심지어 같은 인터프리터를 위해서든요. 하지만 어떤 호스트 스레드에 대해서도, 그 스레드에 결합된 스레드 상태 중 하나만 한 번에 그 스레드가 사용할 수 있어요.

스레드 상태는 서로 격리되고 독립적이며, 아마도 인터프리터와 그 인터프리터에 속한 객체나 다른 자원을 공유하는 것 외에는 데이터를 공유하지 않아요.

프로그램이 실행되기 시작하면, threading 모듈을 사용해 새 Python 스레드를 만들 수 있어요(스레드를 지원하는 플랫폼과 Python 구현에서). 추가 프로세스는 os, subprocess, multiprocessing 모듈을 사용해 만들 수 있어요. 인터프리터는 concurrent.interpreters 모듈로 만들고 사용할 수 있어요. 코루틴(async)은 각 인터프리터에서 asyncio로 실행할 수 있는데, 보통 단일 스레드(흔히 메인 스레드)에서만요.

더 알아보기

[1] 이 제한은 이 연산들로 실행되는 코드가 모듈이 컴파일될 당시에는 사용할 수 없기 때문에 발생해요.