💻 [운영체제] 스레드 동기화 - 세마포어와 뮤텍스 완벽 이해
2025. 4. 26. 11:47

💡 동기화(Synchronization) 기본 개념

동기화란 여러 스레드(또는 프로세스)가 동시에 공유 자원에 접근할 때 데이터 충돌이나 레이스 컨디션을 막기 위해 자원 접근을 통제하는 것을 의미한다.

왜 필요한가?

→ 여러 스레드가 같은 변수나 메모리 공간을 수정할 때 충돌이 발생하면, 프로그램이 예측 불가능 하게 동작할 수 있다.

→ 그래서 "한 번에 하나만" 자원을 접근하도록 제어하는 기술이 필요하다.


🔄 동기화 주요 기법: 뮤텍스 vs 세마포어

1. 뮤텍스(Mutex)

  • Mutual Exclusion (상호 배제)에서 온 말
  • 한 번에 하나의 스레드만 자원에 접근할 수 있게 함
  • Lock을 걸고, 작업이 끝나면 Unlock한다
  • 락을 걸었던 스레드만 해제할 수 있다 (권한 있음)

자바에서는

  • synchronized 키워드
  • ReentrantLock 클래스를 통해 구현할 수 있다.

2. 세마포어(Semaphore)

  • 신호등 개념
  • 여러 개(N개)의 스레드가 동시에 자원 접근 가능하게 허용
  • 세마포어는 내부적으로 카운터를 가지고 있다.
    • acquire() → 카운터 -1 (자원 사용)
    • release() → 카운터 +1 (자원 반납)
  • 누구나 release()를 호출할 수 있다 (락 건 스레드가 아니어도)

자바에서는

  • Semaphore 클래스를 사용한다.

🆚 뮤텍스와 세마포어 차이 정리

구분 뮤텍스 세마포어
보호 자원 수 1개 여러개 가능
동작 방식 lock() - unlock() acquire() - release()
해제 권한 락 건 스레드만 해제 누구나 release 가능
사용 목적 하나의 공유 자원 보호 제한된 수량 자원 관리 (ex. 주차장 자리 등)

🍽 실생활 비유

  • 뮤텍스 = 화장실 한 칸
    → 한 사람만 사용 가능, 문 잠가놓고, 끝나면 열어줌.
  • 세마포어 = 주차장 세 자리
    → 최대 3대까지 주차 가능, 하나 나가면 다른 차 들어올 수 있음.

☕️ 자바 코드 예제

뮤텍스 (ReentrantLock 사용)

package os;

import java.util.concurrent.locks.ReentrantLock;

public class MutexExample {
    private static int count = 0; // 공유 자원 (모든 스레드가 접근하려는 변수)
    private static ReentrantLock lock = new ReentrantLock(); // 뮤텍스 역살을 하는 Lock 객체 생성

    public static void main(String[] args) {
        // 각 스레드가 실행할 작업 정의
        Runnable task = () -> {
            lock.lock(); // 공유 자원(count) 접근 전에 락을 건다
            try {
                for (int i = 0; i < 1000; i++) {
                    count++; // count 값을 안전하게 1씩 증가시킨다
                }
            } finally {
                lock.unlock(); // 작업이 끝났으면 락을 반드시 해제한다
            }
        };

        // 두 개의 스레드 생성
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start(); // 첫 번째 스레드 시작
        t2.start(); // 두 번째 스레드 시작

        try {
            t1.join(); // t1 스레드가 끝날 때까지 기다림
            t2.join(); // t2 스레드가 끝날 때까지 기다림
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("최종 count: " + count); // 기대 출력: 2000 (1000 + 1000)

    }

}

세마포어 (Semaphore 사용)

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    static Semaphore parkingLot = new Semaphore(3); // 주차 공간 3개

    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            final int carNum = i;
            new Thread(() -> {
                try {
                    System.out.println("🚗 차량 " + carNum + " 주차 대기 중");
                    parkingLot.acquire();
                    System.out.println("✅ 차량 " + carNum + " 주차 성공");
                    Thread.sleep(2000); // 주차 중
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("🏁 차량 " + carNum + " 주차 끝, 출차");
                    parkingLot.release();
                }
            }).start();
        }
    }
}

📌 최종 정리

  • 동기화는 멀티스레드 환경의 필수 요소다.
  • 뮤텍스는 오직 하나의 자원을 보호할 때 사용한다.
  • 세마포어는 여러 개의 자원을 제한적으로 사용할 때 사용한다.
  • 자바에서는 ReentrantLocksemaphore로 쉽게 구현할 수 있다.

🙏 회고 포인트

  • "왜 동기화가 필요한가?"를 상황 예시와 함께 이해하는 게 중요하다.
  • 단순히 코드 암기 X -> 진짜 적용할 상황을 머릿속에 그려보는 것이 더 중요하다.
  • 실무에서는 DB 커넥션 풀, 멀티 스레드 캐시 관리, API 요청 제한 등에 세마포어가 쓰인다.

📚 전체 예제 및 추가 자료는 GitHub - daily-cs-study에서 확인 가능합니다. 함께 성장해요!