파이썬에서 thread-local data와 관련된 좋은 글이 있어 정리하고자 한다. 원글은 이 글인데 이 사이트에 Threading, Multiprocessing, ThreadPoolExecutor, ProcessPoolExecutor에 대한 설명들이 매우 잘 돼있다.
0. Thread-local data의 필요성
Thread는 프로그램의 실행 단위이며, 파이썬 프로그램은 실행 시 하나의 main thread를 가지고 실행된다. 이때 추가적인 thread가 필요한 상황이 생기는데 파이썬은 threading.Thread
모듈로 이를 지원한다. 이때 모든 thread들은 process가 할당받은 메모리 중 heap, static 등의 영역을 공유하면서 각자의 stack 메모리 공간을 보유하고 있다. 즉, thread별로 고유의 데이터를 보유하는게 가능한데 이러한 데이터를 thread-local object라고 하며, thread-local strage, thread-private, thread-local 등으로 불리운다(본 글에서는 thread-local data의 약자로 TLD라 쓰겠다).
TLD는 기본적으로 get, set이 가능한 객체로 생성된다. TLD는 같은 변수 이름으로 생성이 가능하며 같은 코드를 여러 thread가 각자 실행시키는 형태이다(이는 worker thread를 사용할 때 자주 사용되는 방법이다). 각 TLD는 thread 별로 실행되기 때문에 같은 변수이름이라고 할지라도 다른 메모리 공간에 있는 값이다. 또한 각 thread는 다른 thread의 local data에 접근할 수 없다.
Thread-local data is data whose values are thread specific.
1. Thread-local data 사용 방법들
Thread들은 threading.local()
클래스를 사용하여 local data를 저장할 수 있다.
1) Thread-local data 사용 예제
각 thread별로 local-data를 저장하고 출력하는 예제이다. value에 숫자를 저장하는 예시인데 다른 리스트 등 다른 데이터 형태도 가능하다.
import threading
import time
def each_thread_task(value):
local = threading.local() # TLD object 생성
print(local)
local.value = value # 각 TLD별로 값 할당
time.sleep(1)
print(f"Stored value: {local.value}")
# 메인 thread가 새로운 thread들을 생성하여 each_thread_task 함수 실행
threading.Thread(target=each_thread_task, args=(1,)).start()
time.sleep(0.5)
threading.Thread(target=each_thread_task, args=(2,)).start()
>>>>
<_thread._local object at 0x7f3a2b36b6d0>
<_thread._local object at 0x7f3a2b9a9450>
Stored value: 1
Stored value: 2
print(local)
로 변수를 찍어보면 같은 변수 이름이지만 다른 메모리에 저장되어있는 것을 확인할 수 있다.
2) Thread-local data를 공유하는 예제
TLD를 생성하고 여러 thread들에서 공유하는 예제이다. 앞에서 설명한대로라면 이는 해서는 안될 짓 처럼 보이는데, global 변수와 함께 사용되는 경우나 thread들끼리 상호작용할 때 필요한 방법인 것 같다. TLD는 각 thread별로 private한 특성이 있지만 동시에 여러 thread가 조작할 수 있다.
def shared_thread_task(value, local):
print(local)
local.value = value # local에 값 할당
time.sleep(1)
print(f"Stored value: {local.value}")
# 메인 Thread로 thread-local object 생성
local = threading.local()
threading.Thread(target=shared_thread_task, args=(1,local)).start()
time.sleep(0.5)
threading.Thread(target=shared_thread_task, args=(2,local)).start()
>>>>
Input value: <_thread._local object at 0x7f3a2b36b5e0>
Input value: <_thread._local object at 0x7f3a2b36b5e0>
Stored value: 1
Stored value: 2
마찬가지로 print(local)
로 변수를 찍어보면 동일한 변수에 값을 할당하고 있음을 알 수 있다.
다른 방법으로는 global 변수로 할당해 같은 결과를 얻을 수 있다.
def shared_global_thread_task(value):
global local
print(f"Input value: {local}")
local.value = value # 각 TLD별로 값 할당
time.sleep(1)
print(f"Stored value: {local.value}")
local = threading.local()
threading.Thread(target=shared_global_thread_task, args=(1,)).start()
time.sleep(0.5)
threading.Thread(target=shared_global_thread_task, args=(2,)).start()
>>>>
Input value: <_thread._local object at 0x7f3a2b69f2c0>
Input value: <_thread._local object at 0x7f3a2b69f2c0>
Stored value: 1
Stored value: 2
설명이 매우 부족한데 추가적으로 궁금한 부분들은 꼭 원글을 참고하면 될 것 같다.
'python 메모' 카테고리의 다른 글
[JSON] JSON 파일 읽고 저장하고 예쁘게 프린트하기 (1) | 2022.10.01 |
---|---|
[huggingface] transformers 모델 onnx로 변환하기 (0) | 2022.09.25 |
[pandas] duplicated()의 함정과 모든 중복 데이터 모으기 (0) | 2022.07.31 |
[jupyter] notebook 파일 cli로 중단없이 실행시키기 (1) | 2022.07.30 |
[numpy] np.take, np.take_along_axis (0) | 2022.07.29 |