반응형

프로세스 또는 스레드 간의 동기화공유 자원 관리를 위해 사용하는 동기화 도구

원리인가, 구현인가?

  1. 원리 측면:
    • 뮤텍스와 세마포어는 동시성 제어 문제를 해결하기 위한 개념적인 원리
    • 프로세스 간 공유 자원 접근 문제(Critical Section Problem)를 해결하기 위해 설계
    • "P/V 연산", "락/언락" 등의 수학적 원리에 기반
  2. 구현 측면:
    • 운영 체제는 뮤텍스와 세마포어를 시스템 콜로 구현하여 동기화를 제공
    • 프로그래밍 언어에서는 이를 감싸는 고급 추상화 클래스/메서드로 구현

 

뮤텍스; 단일 스레드/프로세스가 임계 구역을 독점적으로 보호

특징

  • 초기값이 0인 상태에서 스레드가 자원을 점유하면 값을 0 -> 1로 변경.
  • 뮤텍스는 한 번에 하나의 스레드만 자원을 점유할 수 있도록 제한하는 도구
  • 뮤텍스는 자원의 소유권 개념이 있어, 락을 획득한 스레드만 언락이 가능
  • Java의 ReentrantLock

뮤텍스 초기값 0의 의미

  • 0:
    • 뮤텍스가 열려 있는 상태(사용 가능)
    • 어떤 스레드도 해당 뮤텍스를 점유하고 있지 않은 상태

뮤텍스의 동작 과정

  1. 락(Lock):
    • 스레드가 뮤텍스를 점유하려고 하면, 뮤텍스 값을 1로 변경
    • 동시에 다른 스레드가 뮤텍스를 점유하려고 하면 대기 상태로 전환
  2. 언락(Unlock):
    • 스레드가 작업을 완료하고 뮤텍스를 해제하면, 뮤텍스 값을 0으로 
    • 대기 중인 다른 스레드가 뮤텍스를 점유할 수 있도록 허용
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MutexExample {
    private final Lock lock = new ReentrantLock();

    public void criticalSection() {
        lock.lock(); // 임계 구역 접근
        try {
            System.out.println(Thread.currentThread().getName() + " is in critical section.");
        } finally {
            lock.unlock(); // 접근 해제
        }
    }
/////
    public static void main(String[] args) {
        MutexExample example = new MutexExample();

        Runnable task = example::criticalSection;

        // 스레드 2개 실행
        new Thread(task).start();
        new Thread(task).start();
    }
}

 

  • 첫 번째 스레드가 실행되어 lock.lock()으로 락 획득
    • 임계 구역에 진입해 메시지를 출력
    • lock.unlock()로 락 해제
  • 두 번째 스레드는 첫 번째 스레드가 락을 해제하기 전까지 대기
    • 첫 번째 스레드가 락을 해제하면, 두 번째 스레드가 락을 획득하고 임계 구역에 진입

 

  • 임계 구역 보호:
    • ReentrantLock을 사용하여 임계 구역에 동시에 하나의 스레드만 접근하도록 보장
  • 스레드 간 동기화:
    • 두 스레드는 순차적으로 criticalSection 메서드에 접근하며, 동시에 실행되지 않음
  • 락 해제 보장:
    • try-finally 구조를 통해 락이 반드시 해제되도록 보장하여 데드락을 방지

 

 

 

세마포어; 여러 스레드/프로세스가 제한된 자원을 공유

특징

세마포어는 정수 값(카운터)을 기반으로 동작하며, 공유 자원에 대한 접근을 제어하는 데 사용; Java의 Semaphore

 

  • 정수 값 기반 제어:
    • 세마포어는 정수 값을 사용하여 현재 공유 자원에 접근할 수 있는 허용 가능한 스레드 또는 프로세스의 수를 나타냄
    • 이 값은 초기화된 이후, 특정 연산을 통해 증가하거나 감소
  • P 연산 (wait 또는 acquire):
    • 세마포어 값을 감소
    • 값이 0보다 작아질 경우, 현재 프로세스나 스레드는 대기 상태에 들어가고, 다른 스레드가 세마포어 값을 증가시킬 때까지 블록
  • V 연산 (signal 또는 release):
    • 세마포어 값을 증가
    • 대기 중인 스레드가 있으면 이를 깨워서 실행

세마포어의 초기값

세마포어의 초기값은 관리할 자원의 개수를 나타냄

  • 초기값 1: 바이너리 세마포어(Binaray Semaphore)처럼 동작하여 뮤텍스와 비슷한 역할
  • 초기값 N (N > 1): 자원을 N개까지 동시 허용하는 카운팅 세마포어(Counting Semaphore) 역할
  • 세마포어는 다음과 같은 연산으로 동작한다:
    • P(Wait):
      • 자원의 개수를 감소시킴.
      • 값이 0 이하가 되면 스레드는 자원이 해제될 때까지 대기.
    • V(Signal):
      • 자원의 개수를 증가시킴.
      • 대기 중인 스레드가 있다면 해제된 자원을 할당받아 실행을 재개.

세마포어의 값과 자원의 상태

  1. 값 > 0:
    • 자원이 하나 이상 사용 가능한 상태
    • 대기 중인 스레드가 즉시 자원에 접근 가능.
  2. 값 = 0:
    • 모든 자원이 이미 사용 중
    • 자원을 사용하려는 스레드는 대기 상태로 전환
  3. 값 < 0 (특정 구현에서 허용):
    • 대기 중인 스레드의 수를 나타낼 수 있
    • 하지만 일반적인 세마포어 구현에서는 음수값을 사용하지 않고 **대기열(queue)**로 관리

 

세마포어의 용도

  1. 임계 구역(Critical Section) 보호:
    • 공유 자원에 대한 동시 접근을 제어하여 데이터 무결성을 보장합니다.
  2. 스레드 동기화:
    • 여러 스레드가 특정 순서대로 작업을 수행하도록 제어합니다.
  3. 제한된 자원 관리:
    • 예를 들어, 네트워크 연결, 데이터베이스 연결과 같이 제한된 수의 자원을 사용하는 경우, 세마포어를 사용하여 접근 수를 제한할 수 있습니다.

 

  • 데이터베이스 커넥션 풀 관리
  • 생산자-소비자 문제
  • 네트워크 리소스 관리 (동시에 처리 가능한 연결 제한)
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private final Semaphore semaphore = new Semaphore(2); // 동시에 2개 허용

    public void accessResource() {
        try {
            semaphore.acquire(); // P 연산
            System.out.println(Thread.currentThread().getName() + " accessing resource.");
            Thread.sleep(1000); // 작업 수행
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release(); // V 연산
            System.out.println(Thread.currentThread().getName() + " released resource.");
        }
    }
////
    public static void main(String[] args) {
        SemaphoreExample example = new SemaphoreExample();

        Runnable task = example::accessResource;

        for (int i = 0; i < 5; i++) {
            new Thread(task).start(); // 스레드 5개 실행
        }
    }
}
/////////
Thread-0 accessing resource.
Thread-1 accessing resource.
Thread-0 released resource.
Thread-2 accessing resource.
Thread-1 released resource.
Thread-3 accessing resource.
Thread-2 released resource.
Thread-4 accessing resource.
Thread-3 released resource.
Thread-4 released resource.
  • 초기 상태에서 Semaphore의 카운터는 2
  • 5개의 스레드가 실행되며, 다음과 같이 동작
  1. Thread-0Thread-1이 먼저 자원을 획득하여 작업을 수행
  2. 다른 스레드(Thread-2, Thread-3, Thread-4)는 자원이 반환될 때까지 대기
  3. 작업이 끝난 스레드가 자원을 반환(release())하면, 대기 중인 스레드 중 하나가 자원을 획득
  4. 이 과정이 반복되며, 모든 스레드가 차례로 작업을 완료

 

 

뮤텍스와 바이너리 세마포어는 같은 것? NO

  • 뮤텍스는 락을 가진 자만 해제 가능, 세마포어는 그렇지 않다.
  • 뮤텍스는 priority inheritance 속성을 가지나 세마포어는 그렇지 않다(누가 시그널을 날릴지 모름)
    • 우선순위 높은 작업이 락에 의해 블라킹되면 그 블라킹 작업의 우선순위를 높여 우선순위작업이 빨리 처리되게끔 하는 것

 

상호배제(단일 자원에 대한 독점적 접근 보장)만 필요하다면 뮤텍스를, 작업 간의 실행순서 동기화가 필요하면 세마포어 사용

언제 뮤텍스를 사용할까?

  • 자원에 대한 단일 접근만 보장하면 되는 경우.
  • 예:
    • 공유 변수나 데이터 구조 보호.
    • 파일 쓰기 작업.

언제 세마포어를 사용할까?

  • 작업 간 실행 순서를 동기화해야 하는 경우.
  • 여러 스레드가 동시에 제한된 자원(예: DB 연결, 네트워크 포트)에 접근할 때.
  • 예:
    • 생산자-소비자 문제.
    • 연결 풀 관리(최대 N개 동시 연결).
      • 디비풀; 커넥션풀
728x90
반응형

+ Recent posts