프로세스와 스레드의 겉을 핥아보자
목차
프로세스와 스레드
프로세스
운영체제에서 실행 중인 프로그램을 의미하며, 독립된 실행 환경을 갖추고 있다. 프로세스는 운영체제에서 자원을 할당받고, 자신만의 주소 공간을 가진다.
이 주소 공간은 크게 코드(실행 파일), 데이터(초기화된 전역 변수 등), 힙(동적 메모리 할당 공간), 스택(함수 호출, 지역 변수 등) 영역으로 나뉜다.
스레드
프로세스 내에서 실행되는 독립적인 흐름으로, 하나의 프로세스 내에서 여러 스레드가 실행될 수 있다.
스레드는 프로세스의 코드, 데이터, 힙 영역을 공유하지만, 각 스레드는 자기만의 스택을 가진다. 즉, 스레드는 메모리 공간을 일부 공유하고 일부는 독립적으로 사용한다.
따라서 하나의 프로세스 내에서 여러 스레드를 생성해 병렬 혹은 비동기 처리를 할 수 있다.
스레드 간의 동시성(concurrency)을 관리하는 것이 중요하며, 여러 스레드가 공유하는 자원을 안전하게 다루기 위해 동기화가 필요하다.
예를 들어, 동시에 여러 스레드가 동일한 변수에 접근할 때 데이터 경합(data race) 문제가 발생할 수 있다.
- 스레드는 코드, 데이터, 힙 영역을 공유
- 스택은 각 스레드가 독립적으로 가짐
- 스레드의 장점 및 주의사항
- 경량 실행 단위로, 생성/소멸/전환 비용이 낮음
- 스레드 간 동시성(Concurrency) 이슈 존재
스레드 간 동시성 이슈
데이터 경합 (Data Race)
여러 스레드가 동시에 같은 메모리 위치에 접근하여 예측 불가능한 상태가 발생하는 현상
- 원인: 공유 자원에 대한 적절한 동기화 부재
- 문제점: 비결정적 동작, 버그 발생, 디버깅 어려움
- 해결 기법: 동기화 도구 사용
- 락(Lock): 임계 구역에 대한 접근을 제어하는 일반적인 개념으로, 뮤텍스나 세마포어가 대표적 예.
- 뮤텍스(Mutex): 상호 배제를 위한 락으로, 한 번에 하나의 스레드만 공유 자원 접근 가능하게 함.
- 세마포어(Semaphore): 카운팅 기반 락으로, 동시에 접근 가능한 스레드 수를 제한해 여러 스레드가 자원 접근 조절 가능.
- 원자적 연산(Atomic): CPU가 중단 없이 한 번에 처리하는 연산으로, 간단한 변수 조작 시 락 없이도 안전한 동기화 가능.
데드락 (Deadlock)
두 개 이상의 스레드가 서로가 가진 자원을 기다리며 무한 대기 상태에 빠지는 현상
- 원인: 상호 배제(Mutual Exclusion), 점유 및 대기(Hold and Wait), 비선점(No Preemption), 순환 대기(Circular Wait) 조건이 모두 발생
- 문제점: 시스템이나 프로그램이 멈추고 더 이상 진행 불가
- 해결 기법:
- 예방(Prevention): 데드락 발생 조건 중 하나 이상을 사전에 차단하여 데드락을 발생하지 않게 함예: 자원 요청 순서 정하기, 비선점 정책 적용
- 회피(Avoidance): 자원 할당 시 데드락이 발생할 가능성을 미리 계산해 안전 상태인 경우에만 허용예: 뱅커 알고리즘(Banker's Algorithm)
- 발견 및 복구(Detection and Recovery): 데드락 상태를 주기적으로 탐지하고 발견되면 프로세스 종료나 자원 강제 회수 등으로 복구
- 타임아웃 설정(Timeout): 자원 요청 시 일정 시간 이상 대기하면 요청을 취소하거나 프로세스를 중단하여 데드락 해소 시도
라이브락 (Livelock)
스레드들이 계속 서로 방해하며 상태 변경하지만 실제 작업은 진행 안 됨
- 원인: 스레드가 서로 양보하거나 반복 재시도만 계속하는 상황
- 문제점: 작업이 끝나지 않고 무한 반복
- 해결 기법
- 백오프(backoff): 일정 시간 기다렸다가 재시도해서 충돌 가능성 줄이기
- 우선순위 부여: 한쪽 프로세스가 계속 양보하지 않도록 우선순위를 조절
- 락 설계 개선: 락 획득 방식이나 알고리즘 변경으로 경쟁 완화
- 무작위 지연(Random Delay): 재시도 시 랜덤 지연을 넣어 충돌 확률 감소
프로세스와 스레드의 차이점
항목 | 프로세스 | 스레드 |
---|---|---|
주소 공간 | 독립적 | 공유 (스택은 독립) |
자원 할당 | 독립적으로 OS가 할당 | 부모 프로세스 자원 공유 |
생성/종료 비용 | 높음 | 낮음 |
문맥 교환 비용 | 높음 (메모리 맵 전환 필요) | 낮음 (공유 메모리 사용) |
통신 방식 | IPC (파이프, 메시지 큐 등) | 메모리 공유 |
안정성 | 높은 독립성 | 하나의 스레드 오류가 전체에 영향 가능 |
-
프로세스 간의 전환은 메모리 공간 전체를 변경해야 하므로 비용이 크다.
-
스레드는 같은 프로세스 내에서 동일한 캐시 메모리 및 데이터를 공유하기 때문에 캐시 효율성이 높고, 낮은 문맥 교환 비용
-
스레드는 동기화 이슈로 인한 복잡성이 있음.
문맥 교환 (Context Switching)
- CPU가 현재 실행 중인 프로세스 또는 스레드의 상태(context)를 저장하고, 다음 실행할 대상의 상태를 복원하는 작업
- 멀티태스킹 환경에서 여러 작업을 빠르게 전환하기 위해 필수
문맥 (Context)이란?
- 프로세스/스레드가 CPU에서 실행 중이던 현재 상태를 나타내는 정보들의 집합
- 중단된 지점부터 다시 실행할 수 있게 해주는 핵심 실행 상태 정보
- 운영체제는 이 정보를 프로세스 제어 블록(PCB) 에 저장하여 관리
프로세스 제어 블록 (PCB)
- 프로세스별로 운영체제가 관리하는 구조체(데이터 모음)
- 문맥 교환 시 PCB에 현재 문맥을 저장하고,
- 복원할 때 PCB에서 문맥을 읽어 CPU 상태 복구
- PCB에는 문맥뿐 아니라 프로세스 상태, 스케줄링 정보, 열린 파일 정보 등도 포함
PCB 안에 저장되는 문맥(Context) 정보
-
CPU 레지스터 값
산술/논리 연산, 함수 호출 등에 쓰이는 CPU 내부 레지스터 상태
(예:
RAX
,RBX
,RCX
,RSP
,RBP
등 x86 기준) -
프로그램 카운터(PC)
CPU가 다음 실행할 명령어 주소를 가리키는 레지스터
-
스택 포인터(SP)
현재 스택 최상단 주소를 가리키는 레지스터 (함수 호출, 지역 변수 관리)
-
메모리 맵 정보
프로세스 고유 가상 메모리 구조 (페이지 테이블 등)
-
기타 실행 상태 관련 레지스터
문맥 교환 시 변경되는 정보 비교
문맥 정보 | 프로세스 간 교환 | 스레드 간 교환 |
---|---|---|
CPU 레지스터 값 | 저장 및 복원 | 저장 및 복원 |
프로그램 카운터(PC) | 저장 및 복원 | 저장 및 복원 |
스택 포인터(SP) | 저장 및 복원 | 저장 및 복원 |
메모리 맵 정보 (페이지 테이블 등) | 완전 교체 | 공유 (변경 없음) |
프로세스 제어 블록 (PCB) | 전환 대상 PCB로 변경 | 동일 PCB 내 스레드 문맥 변경 |
열린 파일 디스크립터 | 독립적 | 공유 |
힙 영역 | 독립적 | 공유 |
코드 영역 | 독립적 | 공유 |
- 프로세스 전환은 메모리 매핑까지 변경하므로 비용 큼
- 스레드 전환은 주소 공간 공유로 전환 비용 낮음
문맥 교환 오버헤드
- CPU 저장/복원 작업에 시간 소모
- 프로세스 전환 시 캐시 무효화 → 다시 로딩 필요
- 잦은 문맥 교환은 성능 저하 유발
시스템 콜 (System Call)
사용자 프로그램이 커널 기능을 요청하는 인터페이스.
사용자 모드에서 커널 모드로 전환하는 유일한 공식 경로.
프로세스 관련 시스템 콜
시스템 콜 | 기능 |
---|---|
fork() | 현재 프로세스를 복제하여 새 자식 프로세스 생성 |
exec() | 현재 프로세스를 다른 프로그램으로 덮어쓰기 |
wait() | 자식 프로세스 종료까지 대기 |
exit() | 프로세스 종료 및 상태 반환 |
파일 관련 시스템 콜
시스템 콜 | 기능 |
---|---|
open() | 파일 열기 (파일 디스크립터 반환) |
close() | 파일 닫기 |
read() | 파일에서 데이터 읽기 |
write() | 파일에 데이터 쓰기 |
동작 흐름
특징
- 보안성과 안정성을 위해 커널 모드를 통해서만 자원 접근
- 시스템 콜 수행 시에는 사용자-커널 간 모드 전환 오버헤드 발생