Skip to content

프로세스 & 스레드 ​

프로세스는 메모리에 올라와 실행 중인 프로그램이고, 스레드는 그 프로세스 안에서 실제로 일을 하는 실행 흐름입니다. 프로세스를 하나 만들면 기본으로 스레드도 하나(메인 스레드) 같이 생깁니다.

비유: 식당으로 이해하기 ​

프로세스 = 식당 전체
┌─────────────────────────────────┐
│           식당 (Process)         │
│  ┌──────┐ ┌──────┐ ┌──────┐   │
│  │웨이터1│ │웨이터2│ │웨이터3│   │ ← 스레드들
│  │Thread│ │Thread│ │Thread│   │
│  └──────┘ └──────┘ └──────┘   │
│                                 │
│  [공유 주방 = 공유 메모리(Heap)] │ ← 모든 스레드가 같은 Heap 사용!
└─────────────────────────────────┘

이 문서의 위험 포인트MULTI_THREAD_ENABLE=True 상태에서 여러 스레드가 같은 Heap에 동시 접근 → 메모리 누수가 더 빠르게 진행될 수 있음

무엇을 따로 갖고, 무엇을 함께 쓰나 ​

프로세스는 운영체제로부터 각자 독립된 주소 공간을 받습니다. 그 안은 메모리 구조에서 본 것처럼 나뉩니다.

  • Code: 프로그램 명령어
  • Data: 전역 변수, static 변수 (초기화하지 않은 것은 BSS 영역)
  • Heap: 실행 중에 만드는 데이터 (malloc, new, 파이썬 객체 등)
  • Stack: 지역 변수, 매개 변수, 돌아갈 주소

스레드는 이 중 Stack과 레지스터(지금 어디까지 실행했는지 가리키는 PC 포함)만 따로 갖고, Code, Data, Heap은 같은 프로세스의 다른 스레드와 함께 씁니다. 스레드마다 다른 함수를 독립적으로 호출하려면 함수 호출 기록과 지역 변수를 담는 Stack은 따로 있어야 하기 때문입니다.

text
프로세스 A               프로세스 B
┌──────────────┐        ┌──────────────┐
│ Code         │        │ Code         │
│ Data         │  서로  │ Data         │
│ Heap         │  못 봄 │ Heap         │
│ Stack(스레드1)│        │ Stack(스레드1)│
│ Stack(스레드2)│        └──────────────┘
└──────────────┘
 스레드1, 2는 Code/Data/Heap을 함께 씀

파이썬으로 직접 확인해 보면 차이가 바로 보입니다.

python
import threading
import multiprocessing

counter = 0  # 전역 변수 (Data 영역)

def add_one():
    global counter
    counter += 1

if __name__ == "__main__":
    # 스레드: 같은 프로세스 안이라 전역 변수를 함께 씀
    t = threading.Thread(target=add_one)
    t.start(); t.join()
    print("스레드 실행 후 counter =", counter)

    # 프로세스: 메모리를 따로 가지므로 자식이 바꾼 값이 부모에게 안 보임
    p = multiprocessing.Process(target=add_one)
    p.start(); p.join()
    print("프로세스 실행 후 counter =", counter)
text
스레드 실행 후 counter = 1
프로세스 실행 후 counter = 1

스레드가 바꾼 값은 메인 스레드에서도 보여서 1이 됐지만, 자식 프로세스는 자기 메모리 안의 복사본만 바꿨기 때문에 부모의 값은 그대로 1입니다. 프로세스끼리 데이터를 주고받으려면 IPC 같은 별도 통로가 필요합니다.

멀티프로세스 vs 멀티스레드 ​

멀티프로세스는 하나의 프로그램을 여러 프로세스로 나눠 각자 병렬로 일하게 하는 방식이고, 멀티스레드는 하나의 프로세스 안에서 여러 스레드가 나눠 일하는 방식입니다.

항목멀티프로세스멀티스레드
메모리프로세스마다 따로Code/Data/Heap 공유, Stack만 따로
안전성하나가 죽어도 다른 프로세스는 영향 없음 (운영체제가 메모리 침범을 막아 줌)한 스레드가 공유 데이터를 망가뜨리면 프로세스 전체가 멈출 수 있음
자원·속도각자 메모리를 가져서 무겁고, 전환(컨텍스트 스위칭) 비용이 큼공유하는 만큼 생성·전환이 가볍고 데이터 주고받기가 쉬움
데이터 공유IPC(파이프, 소켓, 공유 메모리 등)가 필요전역 변수·Heap으로 바로 공유
주의할 점통신 비용, 메모리 사용량동기화 (경쟁 상태, 데드락)
예크롬이 탭마다 프로세스를 따로 띄움웹 서버가 요청마다 스레드로 처리

컨텍스트 스위칭이 왜 부담일까 ​

CPU 하나는 한 번에 한 흐름만 실행합니다. 다른 프로세스로 바꾸려면 지금 상태(레지스터, 어디까지 실행했는지 등)를 PCB에 저장하고, 다음 프로세스의 상태를 꺼내 복원해야 합니다. 이 과정을 컨텍스트 스위칭이라고 합니다.

프로세스끼리 바꿀 때는 주소 공간 자체가 바뀌므로 캐시와 주소 변환 정보(TLB)가 쓸모없어져 다시 채워야 하는 무거운 작업이 따라옵니다. 스레드끼리는 주소 공간이 같아서 이 부담이 훨씬 작습니다. 자세한 과정은 PCB와 컨텍스트 스위칭에서 다룹니다.

멀티스레드의 약점은 임계 구역으로 막는다 ​

여러 스레드가 같은 데이터를 동시에 바꾸면 결과가 꼬일 수 있습니다. 이 위험한 코드 구간을 임계 구역(Critical Section)이라고 하고, 다음 세 조건을 지키도록 동기화해서 막습니다.

  • 상호 배제: 한 스레드가 임계 구역에 있으면 다른 스레드는 들어올 수 없음
  • 진행: 임계 구역이 비어 있으면 들어가려는 스레드 중 누군가는 들어갈 수 있어야 함
  • 한정된 대기: 들어가려고 기다리는 스레드가 영원히 기다리지 않아야 함

구체적인 예와 해결 도구는 경쟁 상태, 세마포어와 뮤텍스에서 이어서 볼 수 있습니다.

멀티스레드 환경의 추가 위험 ​

Thread 1: malloc(25MB) ──┐
Thread 2: malloc(25MB) ──┼──→ Heap 동시 접근
Thread 3: malloc(25MB) ──┘    (동기화 없으면 더 위험!)

경고 메시지가 나온 이유:
"POTENTIAL DEADLOCK IN CONCURRENT MODE"
→ 스레드들이 서로 자원을 기다리다 멈출 수 있음

정리 ​

개념비전공자 버전 요약
프로세스실행 중인 프로그램. 자기만의 메모리 공간을 가짐
스레드프로세스 안의 일꾼. Stack만 따로 갖고 나머지는 동료와 공유
멀티프로세스안전하지만 무겁고, 데이터를 주고받으려면 IPC가 필요
멀티스레드가볍고 공유가 쉽지만, 동기화를 안 하면 데이터가 꼬임
컨텍스트 스위칭실행 흐름을 바꿀 때 상태를 저장하고 복원하는 작업. 프로세스 전환이 더 비쌈
임계 구역여러 스레드가 동시에 들어가면 안 되는 코드 구간

이 문서의 실습(B4-2)에서는 다음을 확인했습니다.

  • 프로세스는 식당 전체, 스레드는 그 안의 웨이터들이며 모든 스레드가 같은 Heap(공유 주방)을 사용합니다.
  • MULTI_THREAD_ENABLE=True 상태에서 여러 스레드가 같은 Heap에 동시 접근하면 메모리 누수가 더 빠르게 진행될 수 있습니다.
  • 동기화가 없으면 더 위험하고, 스레드들이 서로 자원을 기다리다 멈출 수 있습니다("POTENTIAL DEADLOCK IN CONCURRENT MODE").

출처: Codyssey-B1/B4-2