threading 모듈의 핵심 개념
파이썬의 threading 모듈은 동시성 처리를 위한 기본 도구로, 프로세스와 유사한 인터페이스를 제공합니다. 주로 단일 프로세스 내에서 여러 스레드를 생성하여 작업을 병렬로 수행할 수 있도록 합니다.
스레드 생성 방법 두 가지
스레드는 함수 기반과 클래스 기반으로 생성할 수 있습니다.
- 함수 기반:
Thread(target=function, args=())형태로 사용 - 클래스 기반:
Thread상속 후run()메서드 오버라이드
예시: 함수 기반 스레드 생성
from threading import Thread
import time
def greet(name):
time.sleep(1)
print(f"{name} 안녕하세요")
if __name__ == "__main__":
thread = Thread(target=greet, args=("김철수",))
thread.start()
print("메인 스레드 실행 중...")
예시: 클래스 기반 스레드 생성
from threading import Thread
import time
class GreetingThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
time.sleep(1)
print(f"{self.name} 인사 완료")
if __name__ == "__main__":
worker = GreetingThread("이영희")
worker.start()
print("메인 스레드 종료 대기")
스레드와 프로세스의 차이점
동일한 프로세스 내에서 스레드는 공유된 메모리 공간을 사용하지만, 프로세스는 독립된 메모리 영역을 갖습니다.
스레드 생성 속도 비교
from threading import Thread
from multiprocessing import Process
import os
def task():
print(f"작업 실행 - 프로세스 ID: {os.getpid()}")
if __name__ == "__main__":
# 스레드 생성
t = Thread(target=task)
t.start()
print("메인 스레드 완료")
# 프로세스 생성
p = Process(target=task)
p.start()
print("메인 프로세스 완료")
공유 데이터의 특성
from threading import Thread
counter = 100
def decrease():
global counter
temp = counter
counter = temp - 1
if __name__ == "__main__":
threads = []
for _ in range(50):
t = Thread(target=decrease)
t.start()
threads.append(t)
for t in threads:
t.join()
print(f"최종 값: {counter}") # 예상값: 50 (실제는 비결정적)
스레드 제어 및 상태 확인
is_alive(): 스레드가 실행 중인지 확인getName(),setName(): 스레드 이름 설정 및 조회threading.current_thread(): 현재 실행 중인 스레드 객체 반환threading.enumerate(): 현재 활성화된 스레드 목록 반환threading.active_count(): 실행 중인 스레드 수 반환
스레드 종료 대기 (join)
from threading import Thread
import time
def worker():
time.sleep(2)
print("작업 완료")
if __name__ == "__main__":
thread = Thread(target=worker)
thread.start()
thread.join() # 메인 스레드는 이 스레드 종료까지 대기
print("메인 스레드 종료")
데몬 스레드 (Daemon Thread)
데몬 스레드는 메인 스레드 종료 시 자동으로 종료됩니다. 반드시 setDaemon(True)를 start() 전에 설정해야 합니다.
from threading import Thread
import time
def background_task():
time.sleep(3)
print("백그라운드 작업 완료")
if __name__ == "__main__":
daemon_thread = Thread(target=background_task)
daemon_thread.setDaemon(True)
daemon_thread.start()
print("메인 스레드 종료")
# 백그라운드 스레드는 메인 종료 후 즉시 종료됨
GIL (Global Interpreter Lock) 이해
파이썬은 한 번에 하나의 스레드만 실행하도록 보장하는 GIL을 사용합니다. 이는 멀티스레딩 성능을 제한하지만, 간단한 동기화를 위해 필요합니다.
동기화 락 (Lock)
공유 자원 접근을 보호하기 위해 threading.Lock()을 사용합니다.
from threading import Thread, Lock
shared_value = 0
lock = Lock()
def increment():
global shared_value
with lock:
temp = shared_value
shared_value = temp + 1
if __name__ == "__main__":
threads = [Thread(target=increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"최종 값: {shared_value}") # 항상 10 출력
데드락과 재입장 가능 락 (RLock)
자신이 이미 잠금을 획득한 상태에서 다시 잠금을 시도하면 발생하는 데드락을 방지하기 위해 RLock 사용.
from threading import RLock
rlock = RLock()
def nested_function():
with rlock:
with rlock: # 가능! RLock는 재진입 허용
print("중첩 잠금 성공")
if __name__ == "__main__":
thread = Thread(target=nested_function)
thread.start()
thread.join()
Event 객체를 통한 스레드 조절
Event는 특정 조건이 만족될 때까지 스레드를 일시 정지시키는 데 사용됩니다.
from threading import Thread, Event
import time
event = Event()
def wait_for_signal():
print("기다리는 중...")
event.wait() # 이벤트가 설정될 때까지 대기
print("신호 수신! 작업 시작")
def send_signal():
time.sleep(2)
event.set() # 신호 발송
print("신호 전송 완료")
if __name__ == "__main__":
t1 = Thread(target=wait_for_signal)
t2 = Thread(target=send_signal)
t1.start()
t2.start()
t1.join()
t2.join()
concurrent.futures를 활용한 스레드/프로세스 풀
작업을 큐에 넣고 자동으로 관리하는 고급 패턴입니다.
from concurrent.futures import ThreadPoolExecutor
import time
def compute(n):
time.sleep(1)
return n * n
if __name__ == "__main__":
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(compute, i) for i in range(5)]
for future in futures:
result = future.result()
print(f"결과: {result}")