Skip to content

Chapter 04. 동기화, IPC, 스케줄링

멀티스레드 환경에서 발생하는 **레이스 컨디션(Race Condition)**과 임계 구역(Critical Section) 문제를 살펴보고, 이를 해결하기 위한 동기화(Synchronization) 기법을 다룬다. 또한 프로세스 간 데이터를 주고받는 IPC(Inter-Process Communication) 메커니즘과, CPU를 효율적으로 배분하는 스케줄링(Scheduling) 전략까지 학습한다.


4.1 레이스 컨디션 (Race Condition)

정의

**레이스 컨디션(Race Condition)**이란 둘 이상의 스레드가 **공유 자원(Shared Resource)**에 동시에 접근하여 서로 **경쟁(Race)**하는 상태를 말한다. 이로 인해 프로그램의 실행 결과가 **비결정적(Nondeterministic)**이 되어, 실행할 때마다 다른 결과가 나올 수 있다.

냉장고 비유

냉장고에 맛있는 음료수를 넣어두었다. 나중에 마시려고 했는데, 다른 가족이 먼저 꺼내서 마셔버렸다. 내가 냉장고를 열면 음료수가 있을 수도 있고, 없을 수도 있다. 이것이 바로 레이스 컨디션이다 --- 결과를 예측할 수 없다.

전역변수 경쟁 예시

전역변수 z_data에 대해 다음과 같은 상황을 생각해보자.

스레드동작
스레드 1z_data = 1000 대입
스레드 2z_data = 2000 대입
스레드 3z_data 값을 읽음

스레드 3이 읽는 값은 1000일 수도, 2000일 수도 있다. 어떤 스레드가 먼저 실행될지는 OS 스케줄러가 결정하기 때문에 아무도 모른다.

한 줄의 C 코드가 위험한 이유

예금 = 예금 + 50000;한 줄의 코드는 사람 눈에는 하나의 동작처럼 보이지만, CPU 입장에서는 최소 3개의 명령어로 분해된다.

단계CPU 명령어동작
1단계MOVE reg, [예금]메모리에서 예금 값을 레지스터로 읽기
2단계ADD reg, 50000레지스터에서 50000을 더하기
3단계MOVE [예금], reg연산 결과를 다시 메모리에 쓰기

3단계 사이에 다른 스레드가 끼어들면 예상치 못한 결과가 발생한다.

스레드 A가 1단계를 마친 후 스레드 B가 끼어들어 전체 과정을 완료하면, 스레드 A는 **이미 오래된 값(100만원)**을 기반으로 계산한다. 결국 3만원이 사라진다.

링크드 리스트에서의 위험

공유 자원이 **링크드 리스트(Linked List)**인 경우 위험은 더욱 심각하다.

  • 추가(Insert) 중에 다른 스레드가 **조회(Read)**하면 아직 연결되지 않은 불완전한 노드를 읽을 수 있다.
  • **삭제(Delete)**는 추가보다 약 3배 복잡하다. 이전 노드의 포인터 변경, 대상 노드 해제 등 여러 단계가 필요하기 때문이다.
  • 삭제 도중 다른 스레드가 해당 노드를 접근하면 **메모리 손상(Memory Corruption)**이나 **크래시(Crash)**로 이어진다.

4.2 임계 구역 (Critical Section)과 동기화

임계 구역이란?

**임계 구역(Critical Section)**이란 공유 자원에 접근하는 코드 구간으로, 동시에 두 개 이상의 스레드가 실행해서는 안 되는 영역이다. 임계 구역 내의 연산은 **원자성(Atomicity)**을 보장해야 한다 --- 즉, 중간에 끼어들기 없이 시작하면 끝까지 완전히 수행되어야 한다.

화장실 비유

화장실을 떠올려보자.

  1. 들어갈 때 문을 잠근다 (Lock)
  2. 볼일을 본다 (임계 구역에서 작업)
  3. 나올 때 문을 연다 (Unlock)

다른 사람은 문이 잠겨있으면 기다렸다가, 열리면 들어간다. 이것이 임계 구역 보호의 기본 원리다.

프린터 비유: 인어공주 문제

두 사람이 하나의 프린터를 공유한다고 하자.

사용자인쇄 내용
사용자 A"공주" 인쇄 중
사용자 B"생선" 인쇄 시도

만약 프린터(공유 자원)에 대한 임계 구역 보호가 없다면, "공주" 인쇄 도중에 "생선"이 끼어들어 **"인어공주"**가 출력될 수 있다. 각자의 인쇄 작업은 시작부터 끝까지 방해받지 않고 완료되어야 한다.

임계 구역의 핵심 원칙

원칙설명
최소화임계 구역은 꼭 필요한 코드만 포함해야 한다. 길어질수록 다른 스레드의 대기 시간이 늘어난다.
없앨 수 있으면 없애라Lock-free 자료구조나 알고리즘을 사용하면 임계 구역 자체를 없앨 수 있다.
길어지면 위험임계 구역이 길어질수록 효율이 저하되고, 데드락(Deadlock) 위험이 증가한다.

Spin Lock: 가장 원시적인 동기화

Spin Lockwhile문으로 플래그(flag)를 반복 확인하며 대기하는 방식이다.

c
// 주의: 개념 설명용 의사코드
volatile int lock = 0;

void enter_critical_section() {
    while (lock == 1) {
        // 계속 반복 확인 (Spinning)
    }
    lock = 1;  // 잠금
}

void leave_critical_section() {
    lock = 0;  // 해제
}

문제점: CPU가 while문을 쉬지 않고 돌리므로 **CPU 점유율이 100%**까지 치솟는다. OS가 제공하는 Spin Lock은 이 문제를 해결한다(짧은 대기 후 스레드를 sleep 상태로 전환).


동기화 기법

모니터(Monitor)와 큐(Queue): 동기화의 결론

현대 동기화의 핵심 패턴은 **큐(Queue)**다.

"세상의 모든 스레드는 Q(큐)를 가지고 있다."

이 말의 의미는 다음과 같다.

  1. 각 스레드에 **작업 큐(Task Queue)**를 연결한다.
  2. 외부에서 해야 할 일이 있으면 해당 스레드의 큐에 **추가(Append)**한다.
  3. 스레드는 자신의 큐에서 하나씩 꺼내서(Dequeue) 처리한다.
  4. 큐에 대한 접근만 통제하면 동기화가 달성된다.

이 패턴을 사용하면 역할별 스레드 분리가 가능하다.

스레드 역할담당
I/O 전담 스레드파일 읽기/쓰기, 네트워크 통신
검색 전담 스레드데이터 검색, 인덱싱
UI 전담 스레드화면 렌더링, 사용자 입력 처리
연산 전담 스레드복잡한 계산, 데이터 가공

이 패턴의 핵심은, 각 스레드가 자신의 큐에서만 꺼내어 처리하므로 공유 자원에 대한 직접적인 경쟁이 사라진다는 것이다. 큐에 항목을 추가하는 동작만 스레드 안전(Thread-safe)하게 구현하면 된다.

이벤트(Event) 객체 기반 동기화 (Windows)

Windows 운영체제에서는 **이벤트 객체(Event Object)**를 사용하여 스레드 간 실행 순서를 명확히 제어할 수 있다.

API역할
CreateEvent()커널 오브젝트(Kernel Object)로 이벤트 생성
SetEvent()이벤트에 신호 보내기 (작업 완료 알림 등)
WaitForSingleObject()이벤트가 신호 상태가 될 때까지 대기 (대기 상태로 전환)

Unix/Linux에서는 이벤트 대신 Signal 기반으로 유사한 동기화를 수행한다.

sleep(0)의 비밀

sleep(0)쉬는 것이 아니다. 현재 스레드를 Ready Queue의 맨 뒤로 보내는 것이다. 즉, 다른 스레드에게 **제어권을 양보(Yield)**하는 동작이다.

호출의미
sleep(1000)1초 동안 대기 상태(Blocked)로 전환
sleep(0)대기하지 않지만, Ready Queue 맨 뒤로 이동하여 다른 스레드에 실행 기회 제공

4.3 교착상태 (Deadlock)

정의

**교착상태(Deadlock)**란 **두 개 이상의 프로세스(또는 스레드)**가 서로 상대방이 점유한 자원을 기다리며 **무한 대기(Infinite Wait)**에 빠진 상태를 말한다. 어느 쪽도 양보하지 않으므로 시스템이 영원히 멈춘다.

교착상태 발생의 4대 조건

교착상태는 다음 네 가지 조건이 모두 충족될 때 발생한다. 하나라도 깨뜨리면 교착상태는 발생하지 않는다.

조건영어설명비유
상호 배제Mutual Exclusion자원을 한 번에 하나의 프로세스만 사용 가능화장실 칸은 한 사람만 사용
점유와 대기Hold and Wait자원을 가진 채로 다른 자원을 대기젓가락 하나 잡고 다른 하나 기다림
비선점No Preemption다른 프로세스의 자원을 강제로 뺏을 수 없음남의 젓가락을 빼앗을 수 없음
원형 대기Circular Wait프로세스들이 원형으로 서로의 자원을 대기A는 B 기다리고, B는 A 기다림

교착상태 디버깅

교착상태가 발생했을 때 원인을 찾는 절차는 다음과 같다.

  1. 덤프(Dump) 획득: 프로세스의 메모리 스냅샷을 파일로 저장
  2. 콜 스택(Call Stack) 분석: 각 스레드가 어디서 멈춰있는지 확인
  3. 원인 파악: 어떤 자원에서 서로 기다리고 있는지 순환 구조 확인
  4. 해결: 자원 획득 순서 통일, 타임아웃 설정, 자원 분리 등

4.4 프로세스 간 통신 (IPC - Inter-Process Communication)

IPC가 필요한 이유

Chapter 03에서 배웠듯이, 프로세스의 메모리 공간은 완전히 독립적이다. 운영체제가 외부 접근을 **차단(Protection)**하기 때문이다. 따라서 프로세스끼리 데이터를 주고받으려면 특별한 메커니즘이 필요한데, 이것이 **IPC(Inter-Process Communication)**다.

해킹(Hacking)이란, 바로 이 보호를 뚫는 것이다. iOS 커널 해킹에 성공하면 Apple로부터 버그바운티(Bug Bounty)로 고액 보상을 받을 수 있다. 그만큼 이 보호는 견고하게 설계되어 있다.

IPC 방법론 비교

방법매체특징
파이프(Pipe)파일스트리밍 방식, 직렬화에 유리, 시작은 있지만 끝이 유동적, 크기 자동 증가
공유 메모리(Shared Memory)RAM고정 크기, OS 허락 필요, 양방향, 이벤트/시그널로 동기화
소켓(Socket)네트워크파일의 본질(everything is a file), TCP 기반 통신
RPC (Remote Procedure Call)네트워크원격 함수 호출, 현대에는 HTTP/gRPC로 부활
레지스트리(Registry) (Windows)파일+메모리in-memory 상태, 동시접근 통제, 파일보다 빠름
pragma data_seg (Windows)DLL 공유 섹션DLL 내 전역변수를 여러 프로세스가 공유, injection/후킹 활용

파일 기반 vs RAM 기반 IPC

이 두 가지 유형의 핵심 차이를 이해하는 것이 중요하다.

구분파일 기반 (Pipe, Socket 등)RAM 기반 (Shared Memory)
데이터 형태스트림(Stream) --- 연속적 흐름고정 길이 블록
크기유동적, 자동 증가고정, 생성 시 결정
직렬화필수 (바이트 스트림으로 변환)선택적 (메모리 직접 접근 가능)
OS 검사비교적 느슨크기와 접근 권한을 강하게 검사
속도상대적으로 느림 (커널 경유)빠름 (직접 메모리 접근)

공유 메모리 IPC 흐름

공유 메모리 방식은 가장 빠른 IPC이지만, 동기화를 별도로 구현해야 한다.

다양한 IPC 메커니즘 구조


4.5 CPU 스케줄링

스케줄링 계층

운영체제의 스케줄링은 크게 두 가지 수준으로 나뉜다.

수준영어역할결정 사항
고수준 스케줄링High-level (Job Scheduling)시스템에 진입할 프로세스 수 결정"이 프로그램을 시스템에 들여보낼까?"
저수준 스케줄링Low-level (CPU Scheduling)CPU를 어떤 스레드에 할당할지 결정"지금 누구를 실행할까?"

선점형 vs 비선점형

구분선점형 (Preemptive)비선점형 (Non-preemptive)
정의OS가 CPU를 강제로 빼앗아 다른 프로세스에 할당프로세스가 자발적으로 반납할 때까지 CPU 독점
장점하나의 프로세스가 CPU를 독점하는 것을 방지구현이 단순, 문맥 교환 오버헤드 없음
단점문맥 교환(Context Switch) 비용 발생하나의 프로세스가 전체 시스템을 먹통으로 만들 수 있음
사용대부분의 현대 OS (Windows, Linux, macOS)초기 OS, 일부 임베디드 시스템

프로세스 우선순위

운영체제는 프로세스의 종류에 따라 **우선순위(Priority)**를 부여한다.

PC 환경 vs 서버 환경

환경우선하는 프로세스이유
PC전면(Foreground) 프로세스사용자가 엑셀, 브라우저 등을 쓰려고 OS를 사용하기 때문. 사용자 체감 반응 속도가 최우선.
서버후면(Background) 프로세스서버는 네트워크 요청, DB 쿼리 등 백그라운드 서비스가 핵심. IOCP(I/O Completion Port)가 빠른 이유도 이것.

핵심 정리

주요 용어 정리

용어영어핵심 설명
레이스 컨디션Race Condition여러 스레드가 공유 자원에 동시 접근하여 결과가 비결정적이 되는 상태
임계 구역Critical Section동시 실행이 금지되어야 하는 공유 자원 접근 코드 구간
원자성Atomicity연산이 중간에 끊기지 않고 전부 실행되거나 전혀 실행되지 않는 성질
교착상태Deadlock프로세스들이 서로의 자원을 기다리며 무한 대기에 빠진 상태
모니터/큐Monitor/Queue스레드별 작업 큐를 통해 동기화를 달성하는 패턴
IPCInter-Process Communication독립된 프로세스 간 데이터를 주고받는 메커니즘
공유 메모리Shared Memory가장 빠른 IPC. OS가 허락한 메모리 영역을 프로세스들이 공유
선점형 스케줄링Preemptive SchedulingOS가 CPU를 강제 회수하여 다른 프로세스에 할당하는 방식
비선점형 스케줄링Non-preemptive Scheduling프로세스가 자발적으로 CPU를 반납할 때까지 기다리는 방식
Spin LockSpin Lock반복문으로 잠금 해제를 확인하며 대기하는 원시적 동기화 기법

5줄 요약

  1. 레이스 컨디션은 C 코드 한 줄도 CPU 명령어 3개 이상으로 분해되기 때문에 발생한다 --- 중간에 다른 스레드가 끼어들면 결과를 예측할 수 없다.
  2. 임계 구역은 화장실처럼 Lock/Unlock으로 보호하되, 가능한 한 짧게 유지해야 하며, 없앨 수 있다면 없애라(Lock-free).
  3. 교착상태는 상호 배제, 점유와 대기, 비선점, 원형 대기 4가지 조건이 모두 충족될 때 발생하며, 하나만 깨뜨려도 예방된다.
  4. IPC는 파이프(스트림)와 공유 메모리(고정 블록) 두 축으로 나뉘며, 공유 메모리가 가장 빠르지만 동기화를 직접 구현해야 한다.
  5. CPU 스케줄링은 PC에서는 전면 프로세스를, 서버에서는 백그라운드 프로세스를 우선하며, 현대 OS는 대부분 선점형(Preemptive) 방식을 채택한다.