Published on

쓰레드와 프로세스

Authors
  • avatar
    Name
    devnmin
    Twitter

쓰레드와 프로세스: 운영체제의 핵심 플레이어 🚀

안녕하세요! 오늘은 운영체제의 슈퍼스타, 쓰레드(Thread)프로세스(Process) 에 대해 재미있게 알아보려고 해요. 어려울 것 같지만, 차근차근 살펴보면 그리 어렵지 않답니다!

1. 프로세스와 쓰레드가 필요한 이유 🤔

우리가 컴퓨터로 여러 프로그램을 동시에 실행할 수 있는 것은 프로세스와 쓰레드 덕분이에요!

주요 필요성 ✨

  1. 동시성 처리 - 여러 작업을 한 번에!
  2. 자원 효율성 - CPU와 메모리를 알뜰하게
  3. 응답성 향상 - 빠릿빠릿한 사용자 경험
  4. 자원 공유 - 프로그램끼리 데이터를 주고받기

2. 프로세스(Process): 프로그램의 실행 단위 📦

기술적 장점 ✨

  1. 독립적인 메모리 - 자기만의 공간을 가짐
  2. 높은 안정성 - 다른 프로세스에 영향 X
  3. 보안성 - 데이터 보호가 확실

기술적 한계 😅

  1. 무거운 자원 사용 - 메모리를 많이 먹어요
  2. 느린 컨텍스트 스위칭 - 전환이 느림
  3. 통신 비용 - 프로세스 간 대화가 어려움

프로세스의 메모리 구조 🗂️

프로세스는 다음과 같은 독립적인 메모리 영역을 가집니다:

  1. Code(Text) 영역 📝

    • 실행할 프로그램의 코드가 저장
    • 읽기 전용 영역
    • 프로그램 명령어가 여기 저장됨
  2. Data 영역 💾

    • 전역 변수, static 변수 저장
    • 프로그램이 시작될 때 할당
    • 프로그램 종료 시 삭제
// Data 영역 예시
static int count = 0;  // static 변수
int global = 100;      // 전역 변수
  1. Heap 영역 🎈
    • 동적 메모리 할당 영역
    • 프로그래머가 직접 관리
    • 메모리 누수 주의 필요
# Heap 영역 사용 예시
class Person:
    def __init__(self, name):
        self.name = name

person = Person("Alice")  # 객체는 heap에 할당
  1. 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함수 호출 시자동컴파일러크기 제한 있음

메모리 관리 시 주의사항 ⚠️

  1. Stack Overflow
    • 재귀 호출이 너무 깊을 때
    • 지역 변수가 너무 클 때
def recursive(n):  # 주의: 스택 오버플로우 위험!
    if n == 0: return
    big_array = [0] * 1000000  # 큰 지역 변수
    recursive(n-1)  # 깊은 재귀 호출
  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  # 순환 참조 발생!
  1. Heap Fragmentation
    • 메모리 할당/해제 반복으로 발생
    • 큰 메모리 블록 확보 어려움

3. 쓰레드(Thread): 가벼운 실행 단위 🏃‍♂️

기술적 장점 ✨

  1. 빠른 생성 - 가볍고 빠르게 만들어짐
  2. 효율적인 자원 사용 - 메모리를 공유
  3. 쉬운 통신 - 데이터 공유가 쉬움

기술적 한계 😅

  1. 동기화 이슈 - 데이터 충돌 조심!
  2. 디버깅 어려움 - 버그 찾기가 까다로움
  3. 안정성 위험 - 하나가 망가지면 다 망가질 수 있음

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. 메모리 공유의 장단점 ⚖️

장점 ✨

  1. 빠른 통신 - 메모리 공유로 데이터 교환 빠름
  2. 자원 효율성 - 메모리 중복 사용 방지
  3. 빠른 생성 - 공유 메모리로 인한 빠른 쓰레드 생성

단점 😅

  1. 동기화 필요 - 공유 데이터 접근 시 충돌 가능
  2. 버그 위험 - 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. 실제 활용 시 고려사항 💡

  1. 데이터 공유가 많다면?

    • 쓰레드 사용 권장 (공유 메모리 활용)
  2. 독립적인 작업이라면?

    • 프로세스 사용 권장 (메모리 독립성 보장)
  3. 보안이 중요하다면?

    • 프로세스 사용 권장 (메모리 격리)

마무리 🎯

프로세스와 쓰레드는 현대 프로그래밍의 필수 요소입니다. 각각의 특성을 잘 이해하고 상황에 맞게 사용하면, 더 효율적이고 강력한 프로그램을 만들 수 있어요!

더 자세한 내용이 궁금하시다면 댓글로 남겨주세요. 함께 성장하는 개발자가 되어봐요! 🚀

다음 글 예고: Python의 GIL(Global Interpreter Lock)에 대해 자세히 알아볼 예정이에요! Python에서 멀티쓰레딩이 성능이 안 나오는 이유와 해결 방법에 대해 다룰 거예요. 기대해주세요! ✨