[CS 스터디] 운영체제 정리1

오리구이·2025년 2월 27일

1. 운영체제의 역할

운영체제(Operating System, OS)는 컴퓨터 하드웨어와 소프트웨어 사이에서 자원을 관리하고 프로그램 실행을 돕는 핵심 소프트웨어이다.

운영체제의 핵심 구조: 커널과 시스템 콜

프로세스 및 스레드 관리: 프로그램 실행과 동기화

자원 관리: CPU, 메모리, 파일 시스템 관리



1️⃣ 운영체제의 핵심 구조: 커널과 시스템 콜

📌 커널(Kernel)이란?

커널은 운영체제의 핵심 부분으로, 하드웨어를 직접 제어하며 CPU, 메모리, 입출력 장치 등을 관리하는 역할을 한다.

운영체제는 크게 두 가지 영역으로 나뉜다.

🔹 사용자 영역(User Space): 응용 프로그램(브라우저, 게임 등)이 실행되는 공간

🔹 커널 영역(Kernel Space): 운영체제 핵심 기능이 실행되는 공간

운영체제는 보안과 안정성을 위해 사용자 영역과 커널 영역을 분리하며, 일반 프로그램이 커널 영역에 직접 접근하는 것을 막는다.

📌 시스템 콜(System Call)이란?

사용자 프로그램이 운영체제의 기능을 사용하려면 시스템 콜(System Call)을 호출해야 한다.

즉, 프로그램이 파일을 열거나, 새로운 프로세스를 생성하는 등 중요한 작업을 수행할 때 운영체제의 도움을 요청하는 방식이다.

시스템 콜 과정

1️⃣ 응용 프로그램이 시스템 콜을 호출 🛑 (ex. open()으로 파일 열기)

2️⃣ 사용자 모드 → 커널 모드 전환 🛠️ (운영체제가 개입)

3️⃣ 운영체제 코드 실행 🎯 (파일을 읽고 메모리에 로드)

4️⃣ 시스템 콜 리턴 후 사용자 모드로 복귀 🔁

주요 시스템 콜 종류

📂 파일 관리: open(), close(), read(), write()

📂 디렉터리 관리: mkdir(), rmdir(), chdir()

🔄 프로세스 관리: fork(), execve(), exit(), waitpid()

💡 운영체제는 사용자 프로그램이 직접 커널 영역을 조작하지 못하도록 시스템 콜을 통해 제어한다.



2️⃣ 프로세스 및 스레드 관리: 프로그램 실행과 동기화

📌 프로세스(Process)란?

프로세스는 실행 중인 프로그램을 의미한다.

단순한 프로그램 파일(.exe, .out)은 실행되지 않으면 프로세스가 아니며, 실행될 때 메모리에 적재되고 CPU 자원을 할당받아 실행되는 것이 프로세스이다.

📌 스레드(Thread)란?

스레드는 프로세스 내에서 실행되는 실행 단위이다.

즉, 프로세스가 하나 이상의 스레드를 가질 수 있으며, 여러 스레드가 동시에 실행될 수 있다.



3️⃣ 자원 관리: CPU, 메모리, 파일 시스템 관리

📌 CPU 관리: CPU 스케줄링

CPU 스케줄링이란 여러 프로세스를 효율적으로 실행하기 위해 CPU를 할당하는 방법을 의미한다.

💡 CPU 스케줄링은 멀티태스킹 환경에서 필수적인 개념이며, 성능 최적화에 중요한 역할을 한다.

📌 메모리 관리: 가상 메모리와 페이지 교체

가상 메모리는 실제 물리 메모리보다 큰 공간을 사용할 수 있도록 도와주는 기술이다.

운영체제는 프로그램이 필요할 때만 데이터를 메모리에 로드하며, 이를 통해 효율적인 메모리 사용이 가능하다.

💡 가상 메모리는 제한된 물리 메모리를 효과적으로 활용하는 핵심 기술이다.


📌 파일 시스템: 파일과 디렉터리 관리

운영체제는 파일 시스템을 통해 데이터 저장 및 접근을 관리한다.

💡 운영체제는 파일의 접근 권한을 제어하며, 안정적인 데이터 저장을 보장한다.



2. 프로세스와 스레드: 운영체제의 핵심 개념

운영체제에서 가장 중요한 개념 중 하나는 프로세스와 스레드이다.
이 두 개념을 이해하면, 컴퓨터가 어떻게 프로그램을 실행하고 관리하는지 파악할 수 있다.

프로세스(Process): 실행 중인 프로그램

스레드(Thread): 프로세스 내에서 실행되는 작은 작업 단위

운영체제는 프로세스를 생성하고 관리하며, 각 프로세스가 CPU와 메모리를 적절히 사용할 수 있도록 조정한다.



1️⃣ 프로세스란?

📌 프로세스(Process)의 개념

  • 프로세스(Process)실행 중인 프로그램을 의미한다.

단순히 .exe 또는 .out 같은 파일이 저장되어 있다고 프로세스가 되는 것이 아니다.

📌 프로그램이 실행되어 메모리에 로드되고 CPU가 실행을 담당해야만 프로세스가 된다.

프로세스의 주요 특징

  • 독립적인 메모리 공간을 가짐 (다른 프로세스와 메모리를 공유하지 않음)
  • 운영체제(OS)가 직접 관리 (프로세스 스케줄링, 메모리 할당, 자원 관리)
  • 여러 개의 프로세스가 동시에 실행 가능 (멀티태스킹)

📌 프로세스의 종류

🔹 포그라운드 프로세스(Foreground Process)

  • 사용자가 직접 실행하는 프로세스 (예: 웹 브라우저, 게임, 텍스트 편집기)

🔹 백그라운드 프로세스(Background Process)

  • 사용자가 직접 조작하지 않고 백그라운드에서 실행되는 프로세스
  • 데몬(Daemon): 백그라운드에서 실행되며, 시스템 서비스를 제공 (ex. 리눅스의 cron, sshd)
  • 서비스(Service): 윈도우에서 실행되는 백그라운드 프로세스 (ex. 윈도우 업데이트, 백신 프로그램)


2️⃣ 프로세스의 메모리 구조

프로세스는 실행될 때 운영체제에 의해 특정한 메모리 구조를 가진다.

📌 운영체제는 프로세스가 사용할 메모리를 아래와 같이 4가지 영역으로 구분하여 관리한다.

구분설명
코드(Code) 영역실행할 프로그램 코드가 저장되는 공간 (CPU가 실행할 명령어 포함)
데이터(Data) 영역전역 변수 및 정적 변수가 저장되는 공간
힙(Heap) 영역실행 중 동적으로 할당되는 메모리 (ex. new, malloc 등)
스택(Stack) 영역함수 호출 시 생성되는 지역 변수, 매개변수, 리턴 주소 저장

코드 & 데이터 영역은 "정적 할당", 힙 & 스택 영역은 "동적 할당"이 이루어진다.

💡 주의할 점

  • 힙 메모리를 할당하고 반환하지 않으면 "메모리 누수(Memory Leak)" 발생!
  • 스택 메모리는 함수 호출이 끝나면 자동으로 반환됨.
  • 자바에서는 가비지 컬렉션(GC)이 자동으로 사용되지 않는 힙 메모리를 정리.


3️⃣ PCB(Process Control Block)와 문맥 교환

📌 PCB(Process Control Block)란?

운영체제는 각 프로세스의 정보를 관리하기 위해 PCB(프로세스 제어 블록)를 사용한다.

PCB는 프로세스가 실행 중인 상태를 저장하는 데이터 구조이다.

🔹 PCB에 저장되는 정보

  • 프로세스 ID (PID): 프로세스를 식별하는 고유한 번호
  • 레지스터 값: CPU가 사용하던 값 저장
  • 프로세스 상태: 실행(Running), 대기(Waiting) 등
  • 메모리 관련 정보: 프로세스가 사용하는 메모리 위치
  • 파일 및 입출력 장치 정보: 파일 핸들, 열린 소켓 정보 등

운영체제는 PCB를 이용해 프로세스를 스케줄링하고 관리한다.

📌 문맥 교환(Context Switching) 개념

CPU가 실행할 프로세스를 변경할 때, 현재 프로세스의 상태를 저장하고 새로운 프로세스를 로드하는 과정을 의미한다.

이 과정에서 PCB를 사용하여 프로세스의 실행 상태를 저장하고 복구한다.

문맥 교환이 발생하는 경우

1️⃣ 타이머 인터럽트: 특정 프로세스가 할당된 시간이 끝남

2️⃣ 입출력(I/O) 요청: I/O 작업을 위해 대기 상태로 변경

3️⃣ 시스템 호출(SYS CALL): 프로세스가 커널 기능을 요청

문맥 교환의 주요 작업

  • 실행 중인 프로세스의 레지스터, 프로그램 카운터, 스택 포인터 정보를 PCB에 저장
  • 새로운 프로세스의 PCB 정보를 복원하여 실행 상태로 전환

📌 💡 문맥 교환은 필수적이지만, 불필요한 문맥 교환을 줄이면 성능이 향상된다.



4️⃣ 프로세스 상태

1. 생성(New)

  • 프로세스가 처음 생성된 상태이다.
  • 운영체제가 프로세스를 생성하고 초기화하는 단계이다.
  • 메모리 할당, PCB(Process Control Block) 생성, 입출력 준비 등의 작업이 수행된다.
  • 스케줄러가 프로세스를 준비 상태(Ready)로 이동시켜야 실행 가능하다.

2. 준비(Ready)

  • 실행할 준비가 된 상태이지만 CPU가 할당되지 않아서 대기 중인 상태.
  • CPU 스케줄러가 언제 CPU를 할당할지 결정.
  • 여러 개의 프로세스가 동시에 준비 큐(Ready Queue)에 대기할 수 있다.
  • 문맥 교환(Context Switching)으로 실행 상태(Running)로 전환될 수 있다.

3. 실행(Running)

  • 프로세스가 실제로 CPU를 할당받아 실행 중인 상태.
  • 운영체제가 프로세스를 스케줄링하여 CPU를 배정하면 이 상태가 된다.
  • 하지만, 타이머 인터럽트, 입출력 요청, 강제 종료 등의 이유로 다른 상태로 변경될 수 있다.

4. 대기(Waiting) 또는 블록(Blocked)

  • 입출력(I/O) 작업을 요청하거나 특정 이벤트를 기다리는 상태.
  • CPU를 할당받아도 실행할 수 없기 때문에 강제로 대기해야 한다.
  • 예를 들어, 파일을 읽거나 네트워크에서 데이터를 받을 때 프로세스가 대기 상태가 된다.
  • 이벤트(예: I/O 완료)가 발생하면 다시 준비(Ready) 상태로 이동한다.

5. 종료(Terminated)

  • 프로세스의 실행이 완료되거나 오류로 인해 강제 종료된 상태.
  • 운영체제가 프로세스를 삭제하고, 할당된 자원을 반환한다.
  • 정상적인 종료 외에도 강제 종료(Kill), 오류(Error), 시스템 종료(Shutdown) 등의 이유로 종료될 수 있다.

프로세스 상태 전이(변경)

  1. [New → Ready] : 새로 생성된 프로세스가 실행 준비 상태로 전환.
  2. [Ready → Running] : CPU가 프로세스를 선택하여 실행.
  3. [Running → Ready] : CPU 할당 시간이 끝나거나 인터럽트 발생 → 다시 준비 상태.
  4. [Running → Waiting] : 프로세스가 I/O 요청이나 이벤트를 기다릴 때.
  5. [Waiting → Ready] : 대기하던 이벤트가 완료되면 다시 실행 가능 상태로 전환.
  6. [Running → Terminated] : 프로세스가 정상적으로 종료되거나 강제 종료됨.


5️⃣ 멀티스레드 vs 멀티프로세스

멀티태스킹 환경에서는 여러 개의 프로세스나 스레드가 동시에 실행될 수 있다.

📌 멀티스레드(Multi-thread)란?

멀티스레드는 하나의 프로세스 내에서 여러 개의 스레드가 실행되는 구조이다.

멀티스레드 특징

  • 코드, 데이터, 힙 영역을 여러 스레드가 공유
  • 스택 영역은 각 스레드별로 개별적으로 존재
  • 스레드 간 통신이 빠르고 메모리 효율이 좋음

📌 💡 단점: 스레드 동기화(Synchronization) 필요

📌 멀티프로세스(Multi-process)란?

멀티프로세스는 여러 개의 독립적인 프로세스가 동시에 실행되는 방식이다.

멀티프로세스 특징

  • 각 프로세스가 독립적인 메모리 공간을 가짐
  • 하나의 프로세스가 비정상 종료되어도 다른 프로세스에는 영향 없음

📌 💡 단점: 문맥 교환 비용이 커서 성능이 저하될 수 있음.



6️⃣ 프로세스 간 통신 : IPC

IPC는 운영체제에서 서로 다른 프로세스가 데이터를 주고받는 방식을 의미한다.
멀티프로세스 환경에서는 각 프로세스가 독립적인 메모리 공간을 사용 하기 때문에 직접 데이터를 공유할 수 없다.

공유 메모리(Shared Memory)

공유 메모리는 프로세스 간 특정 메모리 공간을 공유하여 데이터를 주고받는 방법이다.

운영체제(OS)가 공유 메모리 영역을 할당하고, 여러 프로세스가 해당 영역을 읽거나 쓸 수 있도록 한다.

📌 공유 메모리 특징

빠름 → CPU가 직접 메모리에 접근하여 데이터 복사 없이 사용 가능

많은 데이터 처리 가능 → 대량의 데이터 전송에도 유리

동기화 필요 → 여러 프로세스가 동시에 접근하면 충돌 가능 (Mutex, Semaphore 사용)

메모리 관리 필요 → 프로세스 종료 후 메모리를 해제하지 않으면 낭비 발생

IPC 방식의 종류

방식설명장점단점
공유 메모리(Shared Memory)여러 프로세스가 같은 메모리 공간을 공유하여 데이터 전달속도가 빠름 (메모리 직접 접근)동기화 문제 발생 가능 (Mutex, Semaphore 필요)
메시지 큐(Message Queue)운영체제(OS)가 관리하는 큐를 통해 프로세스 간 데이터 전송프로세스 간 독립성 보장속도가 상대적으로 느림
소켓(Socket)네트워크를 통해 서로 다른 프로세스가 통신원격 프로세스와 통신 가능오버헤드가 큼
파이프(Pipe)한 프로세스에서 다른 프로세스로 데이터를 순차적으로 전달간단한 통신 가능단방향 통신(기본), 크기 제한 존재
시그널(Signal)프로세스에 특정 이벤트(알림)를 전달간단한 제어 가능데이터 전달 불가능


3. 동기화 교착 상태


1️⃣ 뮤텍스(Mutex, Mutual Exclusion) 사용

  • 특정 스레드가 파일을 사용하는 동안 다른 스레드는 접근하지 못하도록 잠금(Lock)을 거는 방식.
  • Java에서는 synchronized 키워드 또는 ReentrantLock을 사용할 수 있음.

Java에서 파일 접근을 동기화하는 방법

import java.io.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class SharedFile {
    private static final Lock lock = new ReentrantLock();
    private static final String FILE_PATH = "shared_file.txt";

    public static void writeToFile(String data) {
        lock.lock(); // 🔒 파일 잠금
        try (FileWriter fw = new FileWriter(FILE_PATH, true);
             BufferedWriter bw = new BufferedWriter(fw)) {
            bw.write(data);
            bw.newLine();
            System.out.println(Thread.currentThread().getName() + " wrote: " + data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 🔓 파일 잠금 해제
        }
    }
}

public class RaceConditionExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> SharedFile.writeToFile("Thread A data"));
        Thread t2 = new Thread(() -> SharedFile.writeToFile("Thread B data"));

        t1.start();
        t2.start();
    }
}
  • lock.lock()을 사용하여 한 번에 하나의 스레드만 파일에 접근 가능하도록 제어.
  • lock.unlock()을 사용하여 파일 작업이 끝난 후 다른 스레드가 접근할 수 있도록 해제

2️⃣ 세마포어(Semaphore)란?

  • *세마포어(Semaphore)멀티스레딩 환경에서 여러 스레드가 공유 자원에 접근하는 것을 제어하는 동기화 기법**이다.

특정 개수의 스레드만 공유 자원에 접근할 수 있도록 허용하는 역할을 한다.


📌 세마포어 동작 방식

  1. 초기값(permit 수) 설정
    • permit 값은 공유 자원에 동시에 접근할 수 있는 스레드의 최대 개수.
    • 예를 들어, permit = 2이면 최대 2개의 스레드만 자원에 접근 가능.
  2. 스레드가 공유 자원에 접근할 때 acquire() 실행
    • 세마포어가 0이 아닌 경우, 하나 감소시키고 자원 사용을 허용.
    • 0이면, 다른 스레드가 release()할 때까지 대기 상태.
  3. 작업이 끝난 후 release() 실행
    • 공유 자원을 반환하고 세마포어 값을 증가시켜 다른 스레드가 사용할 수 있도록 허용.

📌 Java 세마포어 예제

다음 예제는 3개의 스레드가 동시에 최대 2개의 스레드만 실행할 수 있도록 세마포어를 설정한 코드이다.

import java.util.concurrent.Semaphore;

class SharedResource {
    private final Semaphore semaphore;

    public SharedResource(int permits) {
        this.semaphore = new Semaphore(permits); // 최대 접근 가능 스레드 개수 설정
    }

    public void useResource(String threadName) {
        try {
            semaphore.acquire(); // 세마포어 획득 (permit 감소)
            System.out.println(threadName + " : 자원 사용 시작...");
            Thread.sleep(2000); // 자원 사용 시간 (2초)
            System.out.println(threadName + " : 자원 사용 종료...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release(); // 세마포어 해제 (permit 증가)
        }
    }
}

public class SemaphoreExample {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource(2); // 최대 2개 스레드 허용

        Thread t1 = new Thread(() -> resource.useResource("Thread A"));
        Thread t2 = new Thread(() -> resource.useResource("Thread B"));
        Thread t3 = new Thread(() -> resource.useResource("Thread C"));

        t1.start();
        t2.start();
        t3.start();
    }
}

📌 실행 결과

(출력 순서는 스레드 스케줄러에 따라 달라질 수 있음)

Thread A : 자원 사용 시작...
Thread B : 자원 사용 시작...
(Thread C는 대기 중)
Thread A : 자원 사용 종료...
Thread B : 자원 사용 종료...
(Thread C가 실행 가능 상태가 됨)
Thread C : 자원 사용 시작...
Thread C : 자원 사용 종료...
  • Thread AThread B동시에 실행됨.
  • Thread C대기 상태에 있다가, 하나의 자원이 반환되면 실행됨.

📌 세마포어를 사용하는 이유

경쟁 상태(Race Condition) 방지

한정된 자원(예: 데이터베이스, 파일, 네트워크 연결) 관리 가능

멀티스레드 환경에서 과부하 방지

예제에서 semaphore(2)을 설정한 이유?

  • 동시에 2개의 스레드만 공유 자원을 사용하도록 제한하여 경쟁 상태를 방지함.

3️⃣ 조건 변수(Condition Variable)와 모니터(Monitor

조건 변수(Condition Variable)모니터(Monitor)는 멀티스레딩 환경에서 스레드 간 동기화를 위한 기법이다.

공유 자원(예: 버퍼, 큐 등)에 여러 스레드가 접근할 때 데이터의 일관성을 유지하는 데 사용된다.


1. 조건 변수(Condition Variable)란?

  • 스레드가 특정 조건이 만족될 때까지 대기(Wait)하고, 조건이 충족되면 실행(Signal)하도록 하는 동기화 기법.
  • wait(), signal(), broadcast() 같은 메서드를 사용하여 구현.
  • 보통 뮤텍스(Mutex)와 함께 사용하여 공유 자원을 보호.

🔹 주요 메서드

  • wait() → 조건이 충족될 때까지 현재 스레드를 대기 상태로 변경.
  • signal() → 하나의 스레드를 깨워서 실행 가능 상태로 변경.
  • broadcast() → 대기 중인 모든 스레드를 깨움.

2. 모니터(Monitor)란?

  • 뮤텍스 + 조건 변수의 조합으로, 공유 자원에 대한 동시 접근을 안전하게 관리하는 동기화 기법.
  • 특정 코드 블록(= 크리티컬 섹션)에 한 번에 하나의 스레드만 접근하도록 제한.

📌 Java에서 synchronized 키워드를 사용하면 모니터 기능을 제공

  • synchronized를 사용하면 하나의 스레드만 해당 블록을 실행 가능.
  • 내부적으로 뮤텍스와 조건 변수 역할을 수행.

📌 조건 변수 & 모니터 예제

🔹 생산자-소비자(Producer-Consumer) 문제 해결

설명:

  • 생산자(Producer): 버퍼에 데이터를 추가. 버퍼가 가득 차면 대기.
  • 소비자(Consumer): 버퍼에서 데이터를 꺼냄. 버퍼가 비어 있으면 대기.

📌 Java 코드 (wait() & notify() 사용)

import java.util.LinkedList;
import java.util.Queue;

class SharedBuffer {
    private final Queue<Integer> buffer = new LinkedList<>();
    private final int MAX_SIZE = 5;

    public synchronized void produce(int item) throws InterruptedException {
        while (buffer.size() == MAX_SIZE) {
            System.out.println("버퍼가 가득 참! 생산자 대기 중...");
            wait();  // 버퍼가 가득 차면 대기
        }
        buffer.add(item);
        System.out.println("생산됨: " + item);
        notify(); // 소비자 깨우기
    }

    public synchronized int consume() throws InterruptedException {
        while (buffer.isEmpty()) {
            System.out.println("버퍼가 비었음! 소비자 대기 중...");
            wait();  // 버퍼가 비었으면 대기
        }
        int item = buffer.poll();
        System.out.println("소비됨: " + item);
        notify(); // 생산자 깨우기
        return item;
    }
}

public class ConditionVariableExample {
    public static void main(String[] args) {
        SharedBuffer buffer = new SharedBuffer();

        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    buffer.produce(i);
                    Thread.sleep(500); // 생산 속도 조절
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    buffer.consume();
                    Thread.sleep(1000); // 소비 속도 조절
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer.start();
        consumer.start();
    }
}

📌 코드 설명

wait()notify()의 역할

  • wait() → 현재 스레드를 대기 상태로 변경 (즉, wait()을 호출한 스레드는 CPU를 점유하지 않고 잠듦).
  • notify()대기 중인 스레드를 깨움 (하나의 스레드만 실행 가능).
  • notifyAll()모든 대기 중인 스레드를 깨움.

실행 흐름

  1. 생산자 스레드가 데이터를 buffer에 추가.

  2. 버퍼가 가득 차면(wait) 생산자는 대기하고, 소비자가 notify() 호출할 때까지 기다림.

  3. 소비자 스레드buffer에서 데이터를 꺼냄.

  4. 버퍼가 비면(wait) 소비자는 대기하고, 생산자가 notify() 호출할 때까지 기다림.

  5. 이 과정을 반복하여 버퍼가 가득 차거나 비었을 때 스레드가 대기 상태로 변경됨.

    스레드 안전(Thread Safety)과 synchronized 키워드


4️⃣ 스레드 안전(Thread Safety)란?

스레드 안전(Thread Safety)이란 멀티스레드 환경에서 여러 스레드가 동시에 공유 자원에 접근할 때, 프로그램이 예상대로 동작하는 것을 의미한다.

스레드 안전한 코드의 특징

  • 데이터 정합성 유지 → 여러 스레드가 공유 자원을 변경해도 데이터가 일관되게 유지됨.
  • 경쟁 조건(Race Condition) 방지 → 공유 자원 접근 순서에 따른 비정상적인 동작 방지.
  • 데드락(Deadlock) 방지 → 여러 스레드가 서로의 락을 기다리며 무한 대기하는 현상 방지.

synchronized 키워드란?

Java에서 synchronized 키워드는 스레드 동기화(Thread Synchronization)를 제공하는 키워드로, 한 번에 하나의 스레드만 특정 블록(또는 메서드)에 접근하도록 보장한다.

synchronized를 사용하면 어떤 효과?

  • 공유 자원에 대한 동시 접근을 제한하여 데이터 정합성을 유지.
  • 동기화된 블록을 한 번에 하나의 스레드만 실행 가능 (뮤텍스 역할

📌 교착 상태(Deadlock)란?

  • 교착 상태(Deadlock)두 개 이상의 프로세스 또는 스레드가 서로 상대방의 자원을 기다리며 무한히 대기하는 상태를 의미한다.

즉, A 프로세스가 B의 자원을 기다리고, B 프로세스는 A의 자원을 기다리는 상황이 발생하면 교착 상태가 된다.


1. 교착 상태 발생 조건 (Necessary Conditions)

미국 컴퓨터 과학자 E. G. Coffman이 제시한 교착 상태 발생 4가지 조건동시에 만족할 때 교착 상태가 발생할 수 있다.

조건설명
1️⃣ 상호 배제 (Mutual Exclusion)한 번에 한 개의 프로세스만 자원을 사용할 수 있어야 한다.
2️⃣ 점유 및 대기 (Hold and Wait)프로세스가 이미 할당된 자원을 유지하면서 추가 자원을 요청할 수 있어야 한다.
3️⃣ 비선점 (No Preemption)프로세스가 자원을 자발적으로 해제하지 않으면 강제로 빼앗을 수 없어야 한다.
4️⃣ 순환 대기 (Circular Wait)두 개 이상의 프로세스가 서로 자원을 점유한 상태에서 순환적으로 대기하는 경우 발생한다.

교착 상태를 해결하려면 이 네 가지 조건 중 하나 이상을 제거해야 한다.


2. 교착 상태 해결 방법

교착 상태를 해결하는 방법은 예방(Prevention), 회피(Avoidance), 탐지(Detection), 복구(Recovery)로 나뉜다.

✅ 1) 예방(Prevention)

🔹 교착 상태 발생 조건 중 하나 이상을 제거하여 발생을 원천 차단하는 방법.

단점: 자원의 활용도가 낮아질 수 있음.

해결 방법교착 상태 발생 조건 제거설명
상호 배제 방지상호 배제 제거모든 자원을 여러 프로세스가 동시에 사용할 수 있도록 함. (ex. 읽기 전용 파일)
점유 및 대기 방지점유 및 대기 제거프로세스가 자원을 요청할 때 모든 자원을 한꺼번에 요청하도록 강제. (ex. 은행가 알고리즘)
비선점 방지비선점 제거자원을 점유한 프로세스가 강제로 자원을 해제하도록 함. (ex. 높은 우선순위 프로세스가 자원을 빼앗음)
순환 대기 방지순환 대기 제거자원에 순서를 부여하여 항상 낮은 번호의 자원부터 요청하도록 강제.

예제 (점유 및 대기 방지)

synchronized (lockA) { // 자원 A 확보
    synchronized (lockB) { // 자원 B 확보
        // 안전한 코드 실행
    }
}

순환 대기를 방지하기 위해 항상 lockA → lockB 순서로 자원을 획득.


✅ 2) 회피(Avoidance)

🔹 교착 상태가 발생할 가능성이 있는 요청을 사전에 차단하는 방법

대표적인 알고리즘: 은행가 알고리즘(Banker's Algorithm)

단점: 자원의 사용이 비효율적일 수 있음.

은행가 알고리즘 개념

  • 프로세스가 자원을 요청하면, 해당 요청이 교착 상태를 유발하는지 검사.
  • 교착 상태가 발생하지 않는 경우에만 자원 할당.

✅ 3) 탐지(Detection) 및 복구(Recovery)

🔹 교착 상태가 발생한 후 이를 탐지하고 복구하는 방법

단점: 교착 상태가 발생한 후 처리하므로 성능 저하 발생 가능.

방법설명
탐지(Detection)- 주기적으로 자원 할당 그래프(Resource Allocation Graph, RAG)를 분석하여 순환 대기 여부 확인.
복구(Recovery)- 교착 상태에 있는 프로세스를 강제 종료하거나, 일부 자원을 강제 회수하여 교착 상태를 해결.

예제 (강제 종료 방법)

if (isDeadlocked()) {
    process.terminate(); // 교착 상태 발생 시 특정 프로세스 종료
}

예제 (자원 선점 방법)

if (isDeadlocked()) {
    process.releaseResource(); // 교착 상태 해소를 위해 자원 반환
}

3. 교착 상태 해결 방법 비교

방법설명장점단점
예방(Prevention)교착 상태 발생 조건 제거확실하게 방지 가능자원의 활용도가 낮아질 수 있음
회피(Avoidance)안전한 상태에서만 자원 할당 (은행가 알고리즘)교착 상태 발생 가능성을 최소화자원 사용이 비효율적
탐지 및 복구(Detection & Recovery)교착 상태 발생 후 탐지 및 해결자원 활용도를 높일 수 있음교착 상태 발생 후 해결해야 하므로 비용이 큼

4. 교착 상태 예제 (Java)

🔹 교착 상태 발생 예제

class DeadlockExample {
    static final Object lockA = new Object();
    static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("Thread 1: lockA 획득");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lockB) {
                    System.out.println("Thread 1: lockB 획득");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lockB) {
                System.out.println("Thread 2: lockB 획득");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lockA) {
                    System.out.println("Thread 2: lockA 획득");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

이 코드는 교착 상태(Deadlock)가 발생함!

  • Thread 1lockA를 획득한 후 lockB를 기다림.
  • Thread 2lockB를 획득한 후 lockA를 기다림.
  • 서로 상대방이 가지고 있는 자원을 기다리며 무한 대기 상태(Deadlock)에 빠짐.

🔹 교착 상태 해결 방법 적용 (순서 고정)

class DeadlockSolution {
    static final Object lockA = new Object();
    static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lockA) { // 항상 lockA → lockB 순서로 획득
                synchronized (lockB) {
                    System.out.println("Thread 1: 작업 완료");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lockA) { // lockA → lockB 순서로 맞춰줌
                synchronized (lockB) {
                    System.out.println("Thread 2: 작업 완료");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

순서를 통일(lockA → lockB)하여 순환 대기를 방지함으로써 교착 상태 해결!


Ref. 📗《이것이 취업을 위한 컴퓨터 과학이다 with CS 기술 면접》, 강민철

0개의 댓글