파이썬의 3가지 스레드 ID
(이 글은 리눅스 환경을 가정합니다.)
간단하게 다음의 코드를 실행하면,
import threading
import os
print(os.getpid(), threading.get_native_id(), threading.current_thread().native_id)
# os.getpid() == 16000
# threading.get_native_id() == 16000 (python 3.8 or later)
# threading.current_thread().native_id == 16000 (python 3.8 or later)
당연히 모든 값이 동일합니다. CPU 입장에서는 사실 스레드와 프로세스의 구분이 없으므로, 그 부분은 운영체제 수준에서 구현하기 나름인데요, 윈도우와는 달리 리눅스는 내부적으로 스레드와 프로세스를 거의 동급으로 취급하기 때문에 저런 결과가 나옵니다.
그렇다면 os.getpid()와 threading.get_native_id(), threading.current_thread().native_id는 어떻게 다를까요? 간단합니다. 하나의 프로세스에서 스레드를 생성해 테스트하면,
def run_thread():
def handler():
print('run_thread', os.getpid(), threading.get_native_id(), threading.current_thread().native_id)
t = threading.Thread(target=handler)
t.daemon = True
t.start()
return t
run_thread()
이렇게 값이 나옵니다.
os.getpid() == 16000
threading.get_native_id(), threading.current_thread().native_id == 16001
당연하겠죠?!!!
그렇다면, threading.get_native_id(), threading.current_thread().native_id 간의 차이점은 뭘까요? 이름상으로는 2개 모두 현재 스레드의 ID를 반환할 것 같은데요, 불행히도 threading.current_thread().native_id에는 한 가지 문제가 있습니다. 아래의 코드를 통해 그것을 재현할 수 있는데,
# 윈도우 개발자를 위한 리눅스 fork 동작 방식 설명 (파이썬 코드)
# ; https://www.sysnet.pe.kr/2/0/12811
pid = os.fork()
if pid == 0:
print(pid, '자식 프로세스의 실행 흐름', os.getpid(), threading.get_native_id(), threading.current_thread().native_id)
# os.getpid() == 16001
# threading.get_native_id() == 16001
# threading.current_thread().native_id == 16000
결과를 보면, threading.get_native_id()는 새로 fork한 자식 프로세스의 thread id를 나타내고 있지만, threading.current_thread().native_id는 (forking 시킨) 부모 프로세스의 스레드 id를 가리킵니다.
따라서 fork를 가정한다면 threading.current_thread().native_id를 사용해서는 안 됩니다. 달리 말해 threading.current_thread().native_id가 왜 있는 것인지 사실 잘 모르겠습니다. ^^;
그런데, 파이썬에는 또 다른 thread id가 있습니다. 닷넷도
Thread.ManagedThreadId가 있는 것처럼, 파이썬 역시 엔진 자체에서 추상화시킨 스레드 ID를 가지고 있는데 이 값은 threading.current_thread().
ident로 구할 수 있습니다.
import threading
print(threading.get_native_id(), threading.current_thread().ident)
# threading.get_native_id() == 160654
# threading.current_thread().ident == 140401214547776
보는 바와 같이 완전히 다른 id 값을 가지므로 숫자 크기만으로 다른 값들과 쉽게 구분할 수 있습니다.
자, 그렇다면 (다른) 스레드의 호출 스택을 구할 때 사용하는
_current_frames의 get 함수에는 어떤 thread id를 전달해야 할까요?
frame = sys._current_frames().get(tid)
print(frame)
어느 정도 예상할 수 있을 텐데요, 파이썬 엔진에서 호출 스택을 구하는 것이므로 threading.current_thread().ident 값이어야 합니다. (이상한 이름 규칙이지만, threading.current_thread().native_id와 threading.get_native_id()는 값이 다른 경우가 있었는데요, 반면 동일한 이름 규칙의 threading.current_thread().ident와 threading.get_ident()는 완전히 동일한 값을 반환합니다.)
ident의 또 한 가지 특징이라면, 위에서 살펴 본 id들은 스레드의 생명이 살아 있는 동안에는 고유한 값을 띄기 때문에 동시에 같은 id가 중복되는 경우는 없습니다. 반면 ident는 파이썬 엔진에서 fork한 프로세스인 경우 동시에 같은 id가 나오기도 합니다.
pid = os.fork()
if pid == 0:
print(pid, '자식 프로세스의 실행 흐름', threading.get_ident()) # 139813516781376
else:
print(pid, '부모 프로세스의 실행 흐름', threading.get_ident()) # 139813516781376
생성 규칙이 뭔지는 모르겠지만, 보는 바와 같이 완전히 동일한 값이 부모/자식 프로세스 사이에서 나타나고 있습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]