- Published on
쓰레드와 프로세스
- Authors
- Name
- devnmin
쓰레드와 프로세스: 운영체제의 핵심 플레이어 🚀
안녕하세요! 오늘은 운영체제의 슈퍼스타, 쓰레드(Thread) 와 프로세스(Process) 에 대해 재미있게 알아보려고 해요. 어려울 것 같지만, 차근차근 살펴보면 그리 어렵지 않답니다!
1. 프로세스와 쓰레드가 필요한 이유 🤔
우리가 컴퓨터로 여러 프로그램을 동시에 실행할 수 있는 것은 프로세스와 쓰레드 덕분이에요!
주요 필요성 ✨
- 동시성 처리 - 여러 작업을 한 번에!
- 자원 효율성 - CPU와 메모리를 알뜰하게
- 응답성 향상 - 빠릿빠릿한 사용자 경험
- 자원 공유 - 프로그램끼리 데이터를 주고받기
2. 프로세스(Process): 프로그램의 실행 단위 📦
기술적 장점 ✨
- 독립적인 메모리 - 자기만의 공간을 가짐
- 높은 안정성 - 다른 프로세스에 영향 X
- 보안성 - 데이터 보호가 확실
기술적 한계 😅
- 무거운 자원 사용 - 메모리를 많이 먹어요
- 느린 컨텍스트 스위칭 - 전환이 느림
- 통신 비용 - 프로세스 간 대화가 어려움
프로세스의 메모리 구조 🗂️
프로세스는 다음과 같은 독립적인 메모리 영역을 가집니다:
Code(Text) 영역 📝
- 실행할 프로그램의 코드가 저장
- 읽기 전용 영역
- 프로그램 명령어가 여기 저장됨
Data 영역 💾
- 전역 변수, static 변수 저장
- 프로그램이 시작될 때 할당
- 프로그램 종료 시 삭제
// Data 영역 예시
static int count = 0; // static 변수
int global = 100; // 전역 변수
- Heap 영역 🎈
- 동적 메모리 할당 영역
- 프로그래머가 직접 관리
- 메모리 누수 주의 필요
# Heap 영역 사용 예시
class Person:
def __init__(self, name):
self.name = name
person = Person("Alice") # 객체는 heap에 할당
- Stack 영역 📚
- 함수 호출, 지역 변수 저장
- 함수 호출 시 자동 할당/해제
- LIFO(Last In First Out) 구조
def calculate(x, y): # x, y는 stack에 저장
result = x + y # result도 stack에 저장
return result
sum = calculate(5, 3) # 함수 종료 시 stack에서 자동 해제
메모리 영역별 특징 비교 📊
영역 | 할당 시점 | 할당 방식 | 관리 주체 | 특징 |
---|---|---|---|---|
Code | 컴파일 시 | 고정 | OS | 읽기 전용 |
Data | 프로그램 시작 시 | 고정 | OS | 전역/정적 데이터 |
Heap | 실행 중 | 동적 | 프로그래머 | 크기 자유롭게 조절 |
Stack | 함수 호출 시 | 자동 | 컴파일러 | 크기 제한 있음 |
메모리 관리 시 주의사항 ⚠️
- Stack Overflow
- 재귀 호출이 너무 깊을 때
- 지역 변수가 너무 클 때
def recursive(n): # 주의: 스택 오버플로우 위험!
if n == 0: return
big_array = [0] * 1000000 # 큰 지역 변수
recursive(n-1) # 깊은 재귀 호출
- Memory Leak
- Heap 메모리 미해제
- 순환 참조 발생
class Node:
def __init__(self):
self.next = None
# 메모리 누수 예시
def create_cycle():
a = Node()
b = Node()
a.next = b
b.next = a # 순환 참조 발생!
- Heap Fragmentation
- 메모리 할당/해제 반복으로 발생
- 큰 메모리 블록 확보 어려움
3. 쓰레드(Thread): 가벼운 실행 단위 🏃♂️
기술적 장점 ✨
- 빠른 생성 - 가볍고 빠르게 만들어짐
- 효율적인 자원 사용 - 메모리를 공유
- 쉬운 통신 - 데이터 공유가 쉬움
기술적 한계 😅
- 동기화 이슈 - 데이터 충돌 조심!
- 디버깅 어려움 - 버그 찾기가 까다로움
- 안정성 위험 - 하나가 망가지면 다 망가질 수 있음
4. 실전 코드로 보는 예제 🛠️
Python에서의 멀티 프로세스
import multiprocessing
import os
def worker(num):
print(f"Process {num}: {os.getpid()}") # 프로세스 ID 출력
if __name__ == "__main__":
processes = []
for i in range(3): # 3개의 프로세스 생성
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
Python에서의 멀티 쓰레드
import threading
def worker(num):
print(f"Thread {num}: {threading.get_ident()}") # 쓰레드 ID 출력
threads = []
for i in range(3): # 3개의 쓰레드 생성
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
5. 실전 활용 가이드 💡
프로세스 사용이 좋을 때
- 독립적인 작업 수행 필요
- 보안이 중요한 작업
- 많은 CPU 연산이 필요할 때
쓰레드 사용이 좋을 때
- 빈번한 데이터 공유가 필요
- 가벼운 작업 처리
- I/O 작업이 많을 때
6. Python 개발자를 위한 특별 팁! 🐍
GIL(Global Interpreter Lock) 이해하기
# CPU 바운드 작업은 멀티프로세스로!
from multiprocessing import Pool
def cpu_bound(n):
return sum(i * i for i in range(n))
if __name__ == '__main__':
with Pool() as p:
result = p.map(cpu_bound, [1000000] * 3)
I/O 바운드 작업 처리
# I/O 바운드 작업은 멀티쓰레드로!
import threading
import requests
def io_bound(url):
response = requests.get(url)
return response.status_code
urls = ['https://example.com'] * 3
threads = [threading.Thread(target=io_bound, args=(url,)) for url in urls]
프로세스와 쓰레드의 메모리 구조 비교 🔄
1. 프로세스의 메모리 구조 🏢
각 프로세스는 완전히 독립된 메모리 영역을 가집니다:
- Code(Text) 영역 📝 - 프로그램 코드
- Data 영역 💾 - 전역/정적 변수
- Heap 영역 🎈 - 동적 할당 메모리
- Stack 영역 📚 - 지역 변수, 함수 호출
# 프로세스 A
global_var = 100 # Data 영역
def process_func(): # Code 영역
local_var = 200 # Stack 영역
data = [1, 2, 3] # Heap 영역
# 프로세스 B는 완전히 다른 메모리 공간을 가짐
2. 멀티 쓰레드의 메모리 공유 👥
같은 프로세스 내의 쓰레드들은 다음을 공유합니다:
✅ 공유하는 영역
- Code 영역 - 프로그램 코드
- Data 영역 - 전역/정적 변수
- Heap 영역 - 동적 할당 메모리
❌ 독립적인 영역
- Stack 영역 - 각 쓰레드의 고유한 스택
import threading
# 모든 쓰레드가 공유하는 변수 (Data 영역)
shared_data = 0
def worker():
global shared_data
local_var = 100 # 각 쓰레드의 독립적인 Stack 영역
shared_data += 1 # Data 영역 공유
# 두 개의 쓰레드 생성
thread1 = threading.Thread(target=worker)
thread2 = threading.Thread(target=worker)
3. 메모리 공유의 장단점 ⚖️
장점 ✨
- 빠른 통신 - 메모리 공유로 데이터 교환 빠름
- 자원 효율성 - 메모리 중복 사용 방지
- 빠른 생성 - 공유 메모리로 인한 빠른 쓰레드 생성
단점 😅
- 동기화 필요 - 공유 데이터 접근 시 충돌 가능
- 버그 위험 - Race Condition 발생 가능
# 동기화 예시
import threading
shared_counter = 0
lock = threading.Lock()
def safe_increment():
global shared_counter
with lock: # 동기화 처리
temp = shared_counter
temp += 1
shared_counter = temp
4. 실제 활용 시 고려사항 💡
데이터 공유가 많다면?
- 쓰레드 사용 권장 (공유 메모리 활용)
독립적인 작업이라면?
- 프로세스 사용 권장 (메모리 독립성 보장)
보안이 중요하다면?
- 프로세스 사용 권장 (메모리 격리)
마무리 🎯
프로세스와 쓰레드는 현대 프로그래밍의 필수 요소입니다. 각각의 특성을 잘 이해하고 상황에 맞게 사용하면, 더 효율적이고 강력한 프로그램을 만들 수 있어요!
더 자세한 내용이 궁금하시다면 댓글로 남겨주세요. 함께 성장하는 개발자가 되어봐요! 🚀
다음 글 예고: Python의 GIL(Global Interpreter Lock)에 대해 자세히 알아볼 예정이에요! Python에서 멀티쓰레딩이 성능이 안 나오는 이유와 해결 방법에 대해 다룰 거예요. 기대해주세요! ✨