파이썬는 Interpreter 언어이기 때문에 기본적으로 싱글 쓰레드에서 순차적으로 동작
→ 병렬처리를 하기 위해서 별도 로직을 구현해야함.
threading 모듈을 사용하여 구현 가능
python의 GIL 때문에 I/O작업(파일 처리, 네트워크 요청)에서 효과적이며, CPU작업에는 multiprocessing이 적합
GIL 이란?
GIL(Global Interpreter Lock)은 Python 인터프리터가 한 번에 하나의 스레드만 실행하도록 제한하는 메커니즘
즉, Python에서 멀티쓰레드를 사용하더라도 한 번에 하나의 스레드만 실행 할 수 있도록 제한
GIL이 존재하는 이유
python은 메모리 관리를 자동화하는 GC(Garbage Collection)을 사용
특히 Referece Counting을 기반으로 객체의 메모리를 관리하는데,
멀티쓰레드 환경에서는 여러 스레드가 동시에 메모리를 변경하면 데이터 충돌이 발생할 가능성이 있음
이를 방지하기 위해 GIL을 사용하여 한 번에 하나의 스레드만 실행하도록 제한한 것
GIL의 문제점
GIL 때문에 멀티쓰레드가 CPU 연산을 병렬로 실행하지 못함
즉, 여러 개의 CPU 코어가 있어도 한 번에 하나의 스레드만 실행되므로 멀티코어를 활용할 수 없음
GIL 우회방법
multiprocessing 모듈을 사용하면 프로세스를 여러 . 개실행하여 GIL의 영향을 받지 않음
각 프로세스는 독립적인 메모리를 가지므로, CPU 코어를 100% 활용 가능
import multiprocessing
import time
def cpu_task():
count = 0
for _ in range(10**7):
count += 1
start = time.time()
processes = []
for _ in range(4): # 4개의 프로세스 생성
p = multiprocessing.Process(target=cpu_task)
p.start()
processes.append(p)
for p in processes:
p.join()
print("멀티프로세스 실행 시간:", time.time() - start)
GIL이 없는 다른 언어 사용
Cython: C 언어로 컴파일하여 GIL을 우회할 수 있음
Jython, IronPython: GIL이 존재하지 않는 Python 구현체
Rust, C++ 등 GIL이 없는 언어 사용
concurrent.futures.ThreadPoolExecutor를 사용하여 간결하게 멀티쓰레드 구현import concurrent.futures
import time
def worker(n):
print(f"Task {n} 시작")
time.sleep(2)
print(f"Task {n} 완료")
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
executor.map(worker, range(10))
장점
max_workers를 조절하여 스레드 개수 관리 가능threading.Thread를 생성하여 실행import threading
import time
def worker(n):
print(f"Thread-{n} 시작"})
time.sleep(2)
print(f"Thread-{n} 완료"})
threads = []
for i in range(10):
t = threading.Thread(target=worker, args=(i,))
t.start()
threads.appendD(t)
for t in thrads:
t.join()
print("모든 작업 완료")
장점
ThreadPoolExecuter보다 더 유연한 설정 가능.단점
| 구분 | 멀티쓰레드 (threading) | 멀티프로세싱 (multiprocessing) |
|---|---|---|
| 실행 단위 | 스레드(Thread) | 프로세스(Process) |
| GIL의 영향 | O (GIL 때문에 CPU 작업에 비효율적) | X (각 프로세스마다 별도 메모리 공간 사용) |
| 적합한 작업 | 네트워크, 파일 I/O | CPU 연산 (데이터 분석, AI 연산 등) |
| 메모리 사용 | 공유 메모리 | 별도 메모리 공간 사용 |
🔹 I/O 작업(파일 읽기/쓰기, 네트워크 요청) → threading (멀티쓰레드)
🔹 CPU 연산이 많은 경우 → multiprocessing 사용 추천
threading.Lock()을 사용하여 공유 데이터 접근을 동기화해야함.import threading
lock = threading.Lock()
def critical_section():
with lock:
# 안전한 공유 데이터 접근
print("공유 데이터 접근")
ThreadPoolExecutor를 추천multiprocessing 사용threading.Lock()으로 동기화를 고려해야함.회고
thread관리가 잘못되면 세션 관리가 안되고, 필요할 때 세션이 종료되림.
(thread, queue 관리 필요)