Python 3.14 "no-GIL"보다 concurrent.interpreters, 그리고 Tcl 데자뷰

🗓️ 19 Nov, 2025

최근의 파이썬은 아주 오랬동안 multicore/parallelism을 지원하기 위해 걸림돌이었던 GIL에서 자유로워진 "no-GIL" 옵션이 베타 단계. ("free GIL" 혹은 "free threading"이라고도 부르는듯) https://blog.jetbrains.com/ko/pycharm/2025/08/faster-python-unlocking-the-python-global-interpreter-lock/

"no-GIL"이 분명히 중요한 변화이겠지만, 내겐 Python 3.14에 concurrent.interpreters 추가된 것이 더 흥미롭다.

이게 과연 Parallelism을 지원하기 위한 방식일까 하고 바로 알아차라기는 어렵지만 조금 생각해보면:

  1. 새로운 프로세스를 만들지는 않는다 (multiprocessing 모듈)
  2. 직접 같은 GIL을 공유하는 스레드를 만들지도 않는다 (threading 모듈)
  3. 하지만, 같은 프로세스 안에서 다른 GIL을 사용하는, 파이썬 인터프리터를 만들고,
  4. 다른 인터프리터와 적당한 정도로 IPC이 가능하고 (Queue 정도)
  5. 다른 인터프리터가 실행했으면 하는 파이썬 코드를 전달 가능.

별거 아닌거 같지만 얻을 수 있는 이점은:

  1. 파이썬에 별다른 변경을 가하지 않아도, 또 기존의 CPython C-API에 의존하는 라이브러리들이 제대로 동작하지 않을까 하는 호환성 걱정 없이,
  2. multiprocessing보다 오버헤드가 적게, 멀티코어를 사용할 수 있다.

As a significant bonus, interpreters are sufficiently isolated that they do not share the GIL, which means combining threads with multiple interpreters enables full multi-core parallelism. (This has been the case since Python 3.12.)

Python스러움, Pythonic한 방식?

파이썬은 다른 동시대의 언어들의 유행과는 조금 다른 길을 가는 파이썬스러운 특유의 방식이 언어, 표준라이브러리(흔히 "batteries"으로 부르는)에 있다고 생각한다.

문법을 확장하기 보다는, 잘 정제된 문법을 갖고, 그 문법에 적용할 수 있는 패턴들을 확장해 쓸 수 있게 해왔다고 생각한다.

Ruby이나 Lisp 계열 언어들이 closure-block을 전달하거나, with- / each--등의 방식으로, 반복자(iterator), 자원관리을 접근할 때에,

Python은 Generator, with-statement, __enter__-magic method 정도로 잘 소화해온 것 같다.

1
2
3
4
5
  File.open("/etc/passwd", "r") do |file|
    file.each_line do |line|
      # ...
    end
  end

…같은 코드라면:

1
2
3
  with open("/etc/passwd", "r") as file:
      for line in file:
          # ...

이렇게 언어 자체를 확장하거나 하지 않으면서 라이브러리 모듈을 통해 dynamic scoping처럼 동작하는 contextvars 모듈도 좋은 예라고 생각한다. 다음 커먼리습 코드1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  (defparameter *xxx* nil)

  (defun print-xxx ()
    (format t "XXX = ~a~%" *xxx*))

  (defun dyn-scoping ()
    (let ((*xxx* 42))
      (print-xxx)))

  (dyn-scoping) ;; ==> "XXX = 42"

이걸 파이썬이었다면2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  import contextvars

  xxx = contextvars.ContextVar('xxx', default='???')

  def print_xxx():
      print(f'XXX = {xxx.get()}')

  with xxx.set('42'):  # Python 3.14+ 전용
      print_xxx()

  print_xxx()

이런예 이외에도, 파이썬은 그 시대의 유행하는 언어들, 혹은 그 언어들의 idiom와는 조금 다르면서도, 파이썬 자기자신스러움을 잃지 않고 발전해왔다고 생각한다.

그리고 이런 측면 때문에 no-GIL 방식의 CPython 자체의 변화보다는, concurrent.interpreters 방식이 더 파이썬스럽고 내겐 흥미롭다고 여겨지는 것 같다. (물론 파이썬 자체 PEP에서 열심히 논의한 결과로 no-GIL을 진행하고 있겠지만 말이다)

(EDIT: 2025-11-20 Thu) concurrent.futures + interpreters

https://docs.python.org/3/library/concurrent.futures.html#interpreterpoolexecutor

The InterpreterPoolExecutor class uses a pool of interpreters to execute calls asynchronously. It is a ThreadPoolExecutor subclass, which means each worker is running in its own thread. The difference here is that each worker has its own interpreter, and runs each task using that interpreter.

The biggest benefit to using interpreters instead of only threads is true multi-core parallelism. Each interpreter has its own Global Interpreter Lock, so code running in one interpreter can run on one CPU core, while code in another interpreter runs unblocked on a different core.

interpreters + threading 이미 바로 쓸 수 있도록 해놓은 모듈이 3.14+ 부터 추가됨. ㅎㅎ 직접 interpreters와 스레드를 묶어서 쓰려고 애쓰지 않아도 바로 쓸 수 있도록.

(부록) Tcl 데자뷰

Tcl도 (파이썬만큼이나 역사가 30년이 넘은 언어이니만큼) GIL 이슈가 동일했다.

그리고 CPython처럼 Tcl도 Reference-Counting 방식의 C-API여서, GIL이 중요하다. (예: CPython의 Py_INCREF / Tcl의 Tcl_IncrRefCount)

하지만, 파이썬과 달리 Tcl은 별로 GIL문제가 크게 부각되지 않아왔었는데(파이썬에 비한다면 사용자층도 거의 남지 않았지만 ㅎㅎ), 이미 예전부터 interpreter 생성과 실행위임 기능을 갖고 있었었고, 스레딩 방식도 아예 이런 분리된 인터프리터를 이용하는 방식이었었다.

  1. https://www.tcl-lang.org/man/tcl8.6/TclCmd/interp.htm
  2. https://www.tcl-lang.org/man/tcl8.6/ThreadCmd/thread.htm

이번 "최신의" 파이썬 모듈 변화를 보면서, 이상하게 오래 된 Tcl의 interp 커맨드, thread 패키지가 기억에서 떠올랐다. (할아버지 감성 👴🏽)

Footnotes


1

Perl 5도 local-을 통해 이를 지원한다.

2

thread-local 같이 동작하게 만들거나, 분리된 call-stack에서는 원래의 값을 유지한다던가 하는 dynamic-scoping의 구현의 수준은 언어별로 많이 다를 것 같다. 하지만 contextvars 정도의 구현수준으로 대부분의 케이스는 커버가 되는 것 같다.