이코에코(Eco²) Knowledge Base/Python
동시성 모델과 Green Thread
mango_fr
2025. 12. 24. 21:06

이 문서는 Python에서 사용 가능한 4가지 동시성 모델을 비교한다.
먼저 동시성을 보다 잘 이해하기 위해 혼동되는 개념인 병렬성과의 차이를 짚고 가자.
┌─────────────────────────────────────────────────────────────┐
│ 동시성 vs 병렬성 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Concurrency (동시성): │
│ ───────────────────── │
│ "여러 작업을 번갈아 가며 처리하는 것" │
│ • 한 번에 하나씩만 실행 │
│ • 빠르게 전환하여 동시에 실행되는 것처럼 보임 │
│ • 예: 싱글 코어에서 멀티태스킹 │
│ │
│ Task A: ██░░░░██░░░░██ │
│ Task B: ░░██░░░░██░░░░██ │
│ ─────────────────────────▶ time │
│ (번갈아 실행) │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ Parallelism (병렬성): │
│ ───────────────────── │
│ "여러 작업을 동시에 실행하는 것" │
│ • 실제로 동시에 실행 │
│ • 멀티코어 필수 │
│ • 예: 멀티코어에서 각 코어가 별도 작업 │
│ │
│ Core 1: ████████████████████ │
│ Core 2: ████████████████████ │
│ ─────────────────────────▶ time │
│ (실제 동시 실행) │
│ │
└─────────────────────────────────────────────────────────────┘
1. 4가지 동시성 모델
Python에서 사용 가능한 동시성 모델:
| 모델 | 단위 | 특징 | 사용 사례 |
|---|---|---|---|
| Process | 프로세스 | 완전 격리, GIL 우회 | CPU-bound |
| Thread | OS 스레드 | 공유 메모리, GIL 영향 | I/O-bound (제한적) |
| Greenlet | 유저 스레드 | Monkey patching, 협력적 | I/O-bound (gevent) |
| Coroutine | 코루틴 | async/await, Event Loop | I/O-bound (asyncio) |
┌─────────────────────────────────────────────────────────────┐
│ 동시성 모델 스펙트럼 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 무거움 가벼움 │
│ (Heavyweight) (Lightweight)│
│ ←─────────────────────────────────────────────────────────→│
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Process │ │ Thread │ │ Greenlet │ │ Coroutine│ │
│ │ (fork) │ │ (OS) │ │ (user) │ │(async/ │ │
│ │ │ │ │ │ │ │ await) │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 특성: │
│ • 메모리 격리 • GIL 영향 • Monkey • Event Loop │
│ • IPC 필요 • Race Patching 필요 │
│ • ~MB 메모리 Condition • 협력적 • 언어 지원 │
│ • ~MB 메모리 멀티태스킹 • ~KB 메모리 │
│ • ~KB 메모리 │
│ │
└─────────────────────────────────────────────────────────────┘
2. Process (프로세스)
2.1 개념
프로세스는 운영체제가 관리하는 독립적인 실행 단위이다.
각 프로세스는 별도의 메모리 공간, 파일 디스크립터, 환경변수를 가진다.
┌─────────────────────────────────────────────────────────────┐
│ Process 구조 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Process 1 │ │ Process 2 │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ Code │ │ │ │ Code │ │ │
│ │ ├───────────┤ │ │ ├───────────┤ │ │
│ │ │ Data │ │ │ │ Data │ │ │
│ │ ├───────────┤ │ │ ├───────────┤ │ │
│ │ │ Heap │ │ │ │ Heap │ │ │
│ │ ├───────────┤ │ │ ├───────────┤ │ │
│ │ │ Stack │ │ │ │ Stack │ │ │
│ │ └───────────┘ │ │ └───────────┘ │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ GIL 1 │ │ │ │ GIL 2 │ │ │
│ │ └───────────┘ │ │ └───────────┘ │ │
│ └─────────────────┘ └─────────────────┘ │
│ ↑ ↑ │
│ │ OS Scheduler │ │
│ └──────────┬───────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ CPU Core(s) │ │
│ └──────────────┘ │
│ │
│ 특징: │
│ • 완전히 격리된 메모리 공간 │
│ • 프로세스별 독립 GIL → CPU-bound 작업에 적합 │
│ • IPC (Inter-Process Communication) 필요 │
│ • fork() 시 메모리 복사 → 오버헤드 큼 │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 Python 예제
from multiprocessing import Process, Queue
def worker(q, n):
"""CPU-intensive 작업"""
result = sum(i * i for i in range(n))
q.put(result)
if __name__ == "__main__":
q = Queue()
processes = []
for i in range(4): # 4개 프로세스 생성
p = Process(target=worker, args=(q, 1000000))
p.start()
processes.append(p)
for p in processes:
p.join()
results = [q.get() for _ in range(4)]
2.3 장단점
| 장점 | 단점 |
|---|---|
| GIL 우회 (진정한 병렬성) | 메모리 오버헤드 (~MB/process) |
| 메모리 격리 (안전) | IPC 필요 (복잡성) |
| 크래시 격리 | fork() 비용 |
| CPU-bound에 최적 | Windows에서 제한적 |
3. Thread (스레드)
3.1 개념
스레드는 프로세스 내에서 실행되는 경량 실행 단위이다.
같은 프로세스의 스레드들은 메모리를 공유한다.
┌─────────────────────────────────────────────────────────────┐
│ Thread 구조 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐│
│ │ Process ││
│ │ ┌──────────────────────────────────────────────────┐ ││
│ │ │ 공유 메모리 │ ││
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ ││
│ │ │ │ Code │ │ Data │ │ Heap │ │ ││
│ │ │ └───────────┘ └───────────┘ └───────────┘ │ ││
│ │ └──────────────────────────────────────────────────┘ ││
│ │ ││
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ││
│ │ │ Thread 1 │ │ Thread 2 │ │ Thread 3 │ ││
│ │ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │ ││
│ │ │ │ Stack │ │ │ │ Stack │ │ │ │ Stack │ │ ││
│ │ │ └────────┘ │ │ └────────┘ │ │ └────────┘ │ ││
│ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ ││
│ │ │ │ │ ││
│ │ └───────────────┼───────────────┘ ││
│ │ │ ││
│ │ ┌─────▼─────┐ ││
│ │ │ GIL │ ← 하나만 실행 가능! ││
│ │ └───────────┘ ││
│ └────────────────────────────────────────────────────────┘│
│ │
│ Python GIL 영향: │
│ • 한 번에 하나의 스레드만 Python 바이트코드 실행 │
│ • CPU-bound에서는 병렬성 없음 │
│ • I/O-bound에서는 I/O 대기 시 GIL 해제 │
│ │
└─────────────────────────────────────────────────────────────┘
3.2 Python 예제
import threading
import time
counter = 0
lock = threading.Lock()
def worker():
global counter
for _ in range(100000):
with lock: # Race condition 방지
counter += 1
threads = [threading.Thread(target=worker) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Counter: {counter}") # 400000
3.3 GIL과 스레드
┌─────────────────────────────────────────────────────────────┐
│ GIL이 Thread에 미치는 영향 │
├─────────────────────────────────────────────────────────────┤
│ │
│ CPU-bound 작업: │
│ ─────────────── │
│ Thread 1: ████....████....████ │
│ Thread 2: ....████....████....████ │
│ Thread 3: ........████........████ │
│ │ │ │ │ │
│ GIL GIL GIL GIL │
│ 전환 전환 전환 전환 │
│ │
│ → 실제 병렬 실행 없음, 전환 오버헤드만 발생 │
│ → 싱글 스레드보다 더 느릴 수 있음! │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ I/O-bound 작업: │
│ ────────────── │
│ Thread 1: ██[I/O 대기........]██[I/O 대기......]██ │
│ Thread 2: ██[I/O 대기....]██[I/O 대기........]██ │
│ Thread 3: ██[I/O 대기......]██[I/O 대기....]██ │
│ │ │ │
│ I/O 시작 시 I/O 완료 시 │
│ GIL 해제 GIL 재획득 │
│ │
│ → I/O 대기 중 다른 스레드 실행 가능 │
│ → 동시성 효과 있음 │
│ │
└─────────────────────────────────────────────────────────────┘
3.4 장단점
| 장점 | 단점 |
|---|---|
| 메모리 공유 (빠른 통신) | GIL로 인한 CPU-bound 제한 |
| 프로세스보다 경량 | Race condition 위험 |
| I/O-bound에서 효과적 | 디버깅 어려움 |
| OS 레벨 스케줄링 | 컨텍스트 스위치 비용 |
4. Green Thread와 Greenlet
4.1 Green Thread란?
Green Thread는 OS 커널이 아닌 런타임/VM/라이브러리가 관리하는 경량 스레드이다.
"Green"이라는 이름은 Sun Microsystems의 Green Team에서 유래했다 (Java 1.0의 초기 스레드 모델).
┌─────────────────────────────────────────────────────────────┐
│ Green Thread vs OS Thread │
├─────────────────────────────────────────────────────────────┤
│ │
│ OS Thread (Native Thread): │
│ ───────────────────────── │
│ • 커널이 스케줄링 │
│ • 커널 모드 전환 필요 (비용 높음) │
│ • OS가 선점적(preemptive) 관리 │
│ • 스레드당 ~MB 스택 메모리 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Kernel Space │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Thread 1 │ │Thread 2 │ │Thread 3 │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────┼────────────┘ │ │
│ │ ▼ │ │
│ │ OS Scheduler │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ Green Thread: │
│ ───────────── │
│ • 런타임/라이브러리가 스케줄링 │
│ • 유저 모드에서 전환 (비용 낮음) │
│ • 협력적(cooperative) 또는 선점적 관리 │
│ • 스레드당 ~KB 스택 메모리 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ User Space │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Runtime / VM / Library │ │ │
│ │ │ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │ │ │
│ │ │ │Green 1│ │Green 2│ │Green 3│ │Green N│ ... │ │ │
│ │ │ └───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘ │ │ │
│ │ │ └─────────┼─────────┼─────────┘ │ │ │
│ │ │ ▼ ▼ │ │ │
│ │ │ User-space Scheduler │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 1-N OS Threads │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 다양한 언어의 Green Thread 구현
| 언어/런타임 | 구현체 | M:N 모델 | 스케줄링 | 특징 |
|---|---|---|---|---|
| Java 1.0 | Green Threads | M:1 | 협력적 | 최초, 현재는 Native |
| Erlang/OTP | Process | M:N | 선점적 | Actor 모델, 수백만 개 |
| Go | Goroutine | M:N | 선점적 (1.14+) | 경량, 채널 통신 |
| Rust | async/await + Tokio | M:N | 협력적 | Zero-cost abstraction |
| Python/Gevent | Greenlet | M:1 | 협력적 | Monkey patching |
| Python/asyncio | Coroutine | M:1 | 협력적 | 언어 지원 |
| Kotlin | Coroutine | M:N | 협력적 | Structured concurrency |
| Lua | Coroutine | M:1 | 협력적 | 단순, 경량 |
4.3 M:N 스레딩 모델
┌─────────────────────────────────────────────────────────────┐
│ M:N 스레딩 모델 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1:1 모델 (Native Thread): │
│ ───────────────────────── │
│ • 1 User Thread = 1 OS Thread │
│ • Python threading, Java (현재) │
│ │
│ User: [T1] [T2] [T3] │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ OS: [OS1] [OS2] [OS3] │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ M:1 모델 (Pure Green Thread): │
│ ───────────────────────────── │
│ • M Green Threads : 1 OS Thread │
│ • Python asyncio, Gevent (단일 스레드) │
│ │
│ User: [G1] [G2] [G3] [G4] [G5] [G6] │
│ │ │ │ │ │ │ │
│ └────┴────┴────┴────┴────┘ │
│ ▼ │
│ OS: [OS1] │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ M:N 모델 (Hybrid): │
│ ───────────────── │
│ • M Green Threads : N OS Threads (M >> N) │
│ • Go goroutine, Erlang process, Java Virtual Threads │
│ │
│ User: [G1][G2][G3][G4][G5][G6][G7][G8][G9][G10]... │
│ │ │ │ │ │ │ │ │ │ │ │
│ └───┴───┴───┼───┴───┴───┼───┴───┴───┘ │
│ ▼ ▼ │
│ OS: [OS1] [OS2] [OS3] │
│ │
│ 장점: │
│ • Green Thread의 경량성 + OS Thread의 병렬성 │
│ • 멀티코어 활용 가능 │
│ • 블로킹 I/O 시에도 다른 OS Thread가 계속 실행 │
│ │
└─────────────────────────────────────────────────────────────┘
4.4 Greenlet: Python의 Green Thread 구현
Greenlet은 Python에서 Green Thread를 구현한 라이브러리이다.
gevent, eventlet 등이 Greenlet을 기반으로 구축되었다.
┌─────────────────────────────────────────────────────────────┐
│ Greenlet 구조 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐│
│ │ Process ││
│ │ ┌──────────────────────────────────────────────────┐ ││
│ │ │ Main Thread │ ││
│ │ │ │ ││
│ │ │ ┌────────────────────────────────────────────┐ │ ││
│ │ │ │ Gevent Hub │ │ ││
│ │ │ │ (메인 Event Loop greenlet) │ │ ││
│ │ │ └─────────────────────┬──────────────────────┘ │ ││
│ │ │ │ │ ││
│ │ │ ┌──────────────────┼──────────────────┐ │ ││
│ │ │ │ │ │ │ ││
│ │ │ ▼ ▼ ▼ │ ││
│ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ ││
│ │ │ │greenlet│ │greenlet│ │greenlet│ │ ││
│ │ │ │ 1 │◄────▶│ 2 │◄────▶│ 3 │ │ ││
│ │ │ │ (task) │ │ (task) │ │ (task) │ │ ││
│ │ │ └────────┘ └────────┘ └────────┘ │ ││
│ │ │ │ │ │ │ ││
│ │ │ └────────────────┼────────────────┘ │ ││
│ │ │ ▼ │ ││
│ │ │ 협력적 전환 │ ││
│ │ │ (switch() 호출 시) │ ││
│ │ └──────────────────────────────────────────────────┘ ││
│ └────────────────────────────────────────────────────────┘│
│ │
│ 특징: │
│ • OS 스레드가 아닌 유저 레벨 구현 (Green Thread) │
│ • 협력적 멀티태스킹 (자발적 전환) │
│ • Monkey patching으로 기존 코드 자동 변환 │
│ • 매우 경량 (~KB/greenlet) │
│ • M:1 모델 (단일 OS 스레드에서 실행) │
│ │
└─────────────────────────────────────────────────────────────┘
4.5 협력적 멀티태스킹 vs 선점적 멀티태스킹
┌─────────────────────────────────────────────────────────────┐
│ 협력적 vs 선점적 멀티태스킹 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 선점적 (Preemptive) - Thread: │
│ ───────────────────────────── │
│ • OS가 강제로 실행 중인 스레드를 중단 │
│ • 스레드는 언제든 중단될 수 있음 │
│ • 예측 불가능한 전환 시점 │
│ │
│ Thread A: ███████|──────────────|███████ │
│ 실행 OS가 강제 중단 재개 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 협력적 (Cooperative) - Greenlet: │
│ ──────────────────────────────── │
│ • greenlet이 자발적으로 제어권 양보 │
│ • I/O 또는 명시적 switch() 시에만 전환 │
│ • 예측 가능한 전환 시점 │
│ │
│ Greenlet A: ███████████████|──────|████████████████ │
│ 실행 (중단 안됨) I/O 다시 실행 │
│ 대기 │
│ │
│ 장점: Race condition 감소, 예측 가능 │
│ 단점: CPU-bound가 다른 greenlet 블로킹 가능 │
│ │
└─────────────────────────────────────────────────────────────┘
4.6 Python 예제 (Gevent)
import gevent
from gevent import monkey
monkey.patch_all() # 표준 라이브러리 패치
import requests # 이제 비동기처럼 동작
def fetch(url):
response = requests.get(url) # 자동으로 greenlet 전환
return response.status_code
# 동시에 여러 요청
urls = [
"https://api.github.com",
"https://api.twitter.com",
"https://api.facebook.com",
]
# gevent.spawn()으로 greenlet 생성
greenlets = [gevent.spawn(fetch, url) for url in urls]
gevent.joinall(greenlets)
results = [g.value for g in greenlets]
4.7 Monkey Patching 상세
┌─────────────────────────────────────────────────────────────┐
│ Monkey Patching 동작 │
├─────────────────────────────────────────────────────────────┤
│ │
│ monkey.patch_all() 호출 시: │
│ │
│ 표준 라이브러리 → Gevent 버전 │
│ ───────────────────────────────────────────── │
│ socket.socket → gevent.socket │
│ ssl.SSLSocket → gevent.ssl │
│ time.sleep → gevent.sleep │
│ select.select → gevent.select │
│ threading.Thread → gevent._threading │
│ os.fork → gevent.os.fork │
│ subprocess.Popen → gevent.subprocess │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 코드 변경 없이 비동기 동작: │
│ │
│ # 원본 코드 (블로킹) │
│ import socket │
│ sock = socket.socket() │
│ sock.connect(("example.com", 80)) │
│ sock.recv(1024) # 블로킹! │
│ │
│ # monkey.patch_all() 후 │
│ # 같은 코드가 자동으로: │
│ # 1. libev에 I/O watcher 등록 │
│ # 2. hub.switch()로 다른 greenlet 실행 │
│ # 3. I/O 완료 시 다시 전환 │
│ │
└─────────────────────────────────────────────────────────────┘
4.8 Green Thread / Greenlet 장단점
| 장점 | 단점 |
|---|---|
| 매우 경량 (~KB) | CPU-bound 블로킹 위험 |
| 기존 동기 코드 재사용 | Monkey patch 부작용 가능 |
| 수천 개 동시 실행 | 일부 C 확장과 호환성 문제 |
| 협력적 → Race condition 감소 | 디버깅 어려움 |
5. Coroutine (코루틴)
5.1 개념
코루틴은 언어 레벨에서 지원하는 동시성 모델이다.
Python에서는 async/await 문법으로 구현된다.
┌─────────────────────────────────────────────────────────────┐
│ Coroutine 구조 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐│
│ │ Process ││
│ │ ┌──────────────────────────────────────────────────┐ ││
│ │ │ Main Thread │ ││
│ │ │ │ ││
│ │ │ ┌────────────────────────────────────────────┐ │ ││
│ │ │ │ asyncio Event Loop │ │ ││
│ │ │ │ │ │ ││
│ │ │ │ ┌──────────────────────────────────────┐ │ │ ││
│ │ │ │ │ Ready Queue │ │ │ ││
│ │ │ │ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │ ││
│ │ │ │ │ │Task 1│ │Task 2│ │Task 3│ ... │ │ │ ││
│ │ │ │ │ │(coro)│ │(coro)│ │(coro)│ │ │ │ ││
│ │ │ │ │ └──────┘ └──────┘ └──────┘ │ │ │ ││
│ │ │ │ └──────────────────────────────────────┘ │ │ ││
│ │ │ │ │ │ │ ││
│ │ │ │ ▼ │ │ ││
│ │ │ │ ┌──────────────────────────────────────┐ │ │ ││
│ │ │ │ │ Selector (epoll/kqueue) │ │ │ ││
│ │ │ │ │ I/O 감시 │ │ │ ││
│ │ │ │ └──────────────────────────────────────┘ │ │ ││
│ │ │ └────────────────────────────────────────────┘ │ ││
│ │ └──────────────────────────────────────────────────┘ ││
│ └────────────────────────────────────────────────────────┘│
│ │
│ 특징: │
│ • async/await 문법 (언어 레벨 지원) │
│ • 명시적 전환 지점 (await) │
│ • Event Loop 필수 │
│ • 매우 경량 (~KB/coroutine) │
│ │
└─────────────────────────────────────────────────────────────┘
5.2 Coroutine의 상태 전이
┌─────────────────────────────────────────────────────────────┐
│ Coroutine 상태 전이 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ await ┌──────────┐ │
│ │ │──────────────▶│ │ │
│ │ RUNNING │ │ SUSPENDED│ │
│ │ │◀──────────────│ │ │
│ └──────────┘ resume └──────────┘ │
│ │ │ │
│ │ return/raise │ I/O 완료 │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ COMPLETED│ │ READY │ │
│ └──────────┘ └────┬─────┘ │
│ │ │
│ │ 스케줄링 │
│ ▼ │
│ ┌──────────┐ │
│ │ RUNNING │ │
│ └──────────┘ │
│ │
│ 예시: │
│ async def fetch(): │
│ # RUNNING │
│ response = await client.get(url) # → SUSPENDED │
│ # I/O 완료 → READY → RUNNING │
│ return response # → COMPLETED │
│ │
└─────────────────────────────────────────────────────────────┘
5.3 Python 예제 (asyncio)
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.json()
async def main():
async with aiohttp.ClientSession() as session:
# 동시에 여러 요청 (gather)
tasks = [
fetch(session, "https://api.github.com/users/octocat"),
fetch(session, "https://api.github.com/repos/python/cpython"),
fetch(session, "https://api.github.com/orgs/python"),
]
results = await asyncio.gather(*tasks)
return results
# Event Loop 실행
results = asyncio.run(main())
5.4 await의 의미
┌─────────────────────────────────────────────────────────────┐
│ await의 동작 │
├─────────────────────────────────────────────────────────────┤
│ │
│ async def my_coroutine(): │
│ print("시작") # 1. 즉시 실행 │
│ await asyncio.sleep(1) # 2. 여기서 제어권 양보 │
│ print("끝") # 3. 1초 후 재개 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ Event Loop 관점: │
│ │
│ 시간 ──────────────────────────────────────────────────▶ │
│ │
│ Coro A: [시작]─await─[대기]────────────────────[끝] │
│ Coro B: ────────────[시작]─await─[대기]────[끝] │
│ Coro C: ─────────────────────────[시작]────────[끝] │
│ │
│ │ │ │ │ │ │
│ t=0 t=1 t=2 t=3 t=4 │
│ │
│ 핵심: await는 "여기서 다른 코루틴 실행해도 됨"을 명시 │
│ │
└─────────────────────────────────────────────────────────────┘
5.5 장단점
| 장점 | 단점 |
|---|---|
| 언어 레벨 지원 (명확한 문법) | async/await 전파 (바이러스성) |
| 명시적 전환 지점 | 기존 동기 코드 수정 필요 |
| 매우 경량 (~KB) | Event Loop 필수 |
| 디버깅 용이 | 일부 라이브러리 미지원 |
| Type hint 지원 | CPU-bound 블로킹 위험 |
6. 컨텍스트 스위치 비용
6.1 비용 비교
┌─────────────────────────────────────────────────────────────┐
│ Context Switch 비용 비교 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 비용 (높음 → 낮음): │
│ │
│ Process ████████████████████████████████ ~1-10ms │
│ │ │
│ ├── TLB flush │
│ ├── 페이지 테이블 전환 │
│ ├── 캐시 무효화 │
│ └── 커널 모드 전환 │
│ │
│ Thread ████████████████████ ~1-100µs │
│ │ │
│ ├── 레지스터 저장/복원 │
│ ├── 스택 전환 │
│ └── 커널 모드 전환 │
│ │
│ Greenlet ████████ ~1-10µs │
│ │ │
│ ├── 스택 전환만 │
│ └── 유저 모드에서 처리 │
│ │
│ Coroutine ████ ~100ns-1µs │
│ │ │
│ └── 상태 저장만 (힙에 저장) │
│ │
└─────────────────────────────────────────────────────────────┘
6.2 메모리 사용량
| 모델 | 기본 메모리 | 10,000개 생성 시 |
|---|---|---|
| Process | ~10-50MB | 100GB+ (불가능) |
| Thread | ~1-8MB (스택) | 10-80GB |
| Greenlet | ~4-8KB | 40-80MB |
| Coroutine | ~2-4KB | 20-40MB |
7. 사용 시나리오
7.1 선택 가이드
┌─────────────────────────────────────────────────────────────┐
│ 모델 선택 가이드 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 작업 유형 판별 │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ ▼ ▼ │
│ CPU-bound? I/O-bound? │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ ┌─────────────┴─────────────┐ │
│ │ Process │ │ │ │
│ │ (multiproc) │ ▼ ▼ │
│ └──────────────┘ 기존 코드 수정 가능? 동기 코드 유지? │
│ │ │ │
│ ┌─────┴─────┐ ┌─────┴─────┐ │
│ ▼ ▼ ▼ ▼ │
│ Yes No Yes No │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Coroutine│ │ Greenlet │ │ Thread │ │
│ │(asyncio) │ │ (gevent) │ │(threading)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
7.2 이코에코 적용 사례
| 컴포넌트 | 모델 | 이유 |
|---|---|---|
| FastAPI (API Layer) | Coroutine | async/await 네이티브 지원 |
| Celery prefork | Process | CPU-bound 가능, 독립 GIL |
| Celery gevent | Greenlet | I/O-bound, 기존 코드 재사용 |
| scan-worker | Greenlet | OpenAI API I/O, 동기 코드 사용 |
8. 핵심 요약
┌─────────────────────────────────────────────────────────────┐
│ 핵심 요약 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Concurrency ≠ Parallelism │
│ ──────────────────────── │
│ • Concurrency: 번갈아 실행 (동시에 하는 척) │
│ • Parallelism: 실제 동시 실행 (멀티코어) │
│ │
│ 2. Green Thread란? │
│ ────────────── │
│ • 커널이 아닌 런타임/라이브러리가 관리하는 경량 스레드 │
│ • Greenlet, Goroutine, Erlang Process 등 │
│ • M:1 또는 M:N 모델로 OS Thread에 매핑 │
│ │
│ 3. 4가지 모델 │
│ ───────────── │
│ • Process: 완전 격리, CPU-bound │
│ • Thread (OS): 공유 메모리, GIL 영향 │
│ • Greenlet (Green Thread): 유저 레벨, Monkey patch │
│ • Coroutine: 언어 지원, async/await │
│ │
│ 4. 선택 기준 │
│ ─────────── │
│ • CPU-bound → Process │
│ • I/O-bound + 새 코드 → Coroutine │
│ • I/O-bound + 기존 코드 → Greenlet (Green Thread) │
│ │
│ 5. 비용 │
│ ────── │
│ • 컨텍스트 스위치: Process > Thread > Greenlet > Coro │
│ • 메모리: Process > Thread > Greenlet ≈ Coroutine │
│ • Green Thread는 유저 모드 전환 → 오버헤드 최소화 │
│ │
└─────────────────────────────────────────────────────────────┘
참고 자료
공식 문서
Green Thread 관련
- Go Goroutines - Go의 Green Thread
- Erlang Processes - Erlang의 Actor 모델
- Java Virtual Threads (JEP 444) - Java 21+ 가상 스레드
- Kotlin Coroutines - Kotlin의 Green Thread
변경 이력
| 날짜 | 내용 |
|---|---|
| 2025-12-24 | 최초 작성 |
| 2025-12-24 | Green Thread 개념 추가 (M:N 모델, 다언어 비교) |