뮤텍스, 세마포어, 모니터

이강용·2024년 7월 25일
0

CS

목록 보기
94/109

뮤텍스(Mutex)란?

  • 뮤텍스(Mutex, Mutual Exclusion)는 여러 스레드가 공유 자원에 접근하는 것을 제어하기 위한 동기화 기법
  • 오직 하나의 스레드만이 임계 구역(Critical Section)에 접근할 수 있도록 보장
  • 다른 스레드가 임계 구역에 접근하려고 하면 현재 스레드가 임계 구역을 벗어날 때까지 대기해야 함

상호배제(Mutual Exclusion)

  • 뮤텍스는 상호배제를 보장
    • 즉, 한 번에 하나의 스레드만 임계 구역에 진입할 수 있음
    • 이를 통해 데이터의 일관성과 무결성을 유지

한정대기(Bounded Waiting)

  • 뮤텍스를 사용할 때, 각 스레드는 제한된 시간 내에 임계 구역에 진입할 수 있는 기회를 가져야 함
  • 특정 스레드가 무한정 대기하는 것을 방지하여 공평성을 보장

진행 상태(Progress)

  • 뮤텍스에서 자원이 사용되지 않을 때, 자원을 필요로 하는 스레드 중 일부가 자원을 얻을 수 있도록 보장
    • 임계 구역이 비어 있을 때는 즉시 다음 스레드가 자원을 사용할 수 있음
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MutexExample {
    // 뮤텍스 객체 생성
    private final Lock mutex = new ReentrantLock();
    private int counter = 0;

    // 임계 구역
    public void increment() {
        // 상호배제를 위해 뮤텍스를 잠금
        mutex.lock();
        try {
            counter++;
            System.out.println(Thread.currentThread().getName() + " incremented counter to " + counter);
        } finally {
            // 임계 구역을 벗어나면 뮤텍스 해제
            mutex.unlock();
        }
    }

    public static void main(String[] args) {
        MutexExample example = new MutexExample();
        
        // 여러 스레드 생성
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread-1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread-2");

        // 스레드 시작
        t1.start();
        t2.start();
    }
}

세마포어(Semaphore)란?

  • 특정 자원에 대한 접근을 제어하기 위해 사용되는 동기화 도구
  • 세마포어는 카운터를 사용하여 여러 스레드가 동시에 자원에 접근할 수 있는 수를 제한함
  • 자원 사용이 끝나면 세마포어 카운터가 증가하고 자원을 사용하기 전에는 카운터가 감소

세마포어의 종류

  • 세마포어는 두 가지 유형으로 나뉨
    • 바이너리 세마포어(Binary Semaphore)와 카운팅 세마포어(Counting Semaphore)

1. 바이너리 세마포어(Binary Semaphore)

  • 카운터가 0과 1 사이의 값을 가질 수 있는 세마포어

  • 뮤텍스와 유사하게 동작하며 한 번에 하나의 스레드만 자원에 접근할 수 있도록 보장

    상호배제(Mutual Exclusion)

  • 바이너리 세마포어는 상호배제를 보장

    • 즉, 카운터가 1일 때만 자원에 접근 가능
    • 카운터가 0이면 다른 스레드는 자원에 접근할 수 없으며 대기해야 함

    한정대기(Bounded Waiting)

  • 각 스레드는 제한된 시간 내에 자원을 사용할 수 있는 기회를 가져야 함

    • 특정 스레드가 무한정 대기하지 않도록 보장

    진행 상태(Progress)

  • 자원이 사용되지 않을 때, 자원을 필요로 하는 스레드가 즉시 자원에 접근할 수 있도록 보장

    • 카운터가 1일 때는 다음 스레드가 자원에 접근 가능
import java.util.concurrent.Semaphore;

public class BinarySemaphoreExample {
    // 바이너리 세마포어 객체 생성, 초기 카운터는 1
    private final Semaphore binarySemaphore = new Semaphore(1);

    public void accessResource() {
        try {
            binarySemaphore.acquire(); // wait()
            System.out.println(Thread.currentThread().getName() + " is accessing the resource.");
            Thread.sleep(1000); // 자원 사용 시간
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " has finished using the resource.");
            binarySemaphore.release(); // signal()
        }
    }

    public static void main(String[] args) {
        BinarySemaphoreExample example = new BinarySemaphoreExample();

        // 여러 스레드 생성
        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                example.accessResource();
            }
        };

        Thread t1 = new Thread(task, "Thread-1");
        Thread t2 = new Thread(task, "Thread-2");

        // 스레드 시작
        t1.start();
        t2.start();
    }
}

2. 카운팅 세마포어(Counting Semaphore)

  • 카운팅 세마포어는 카운터가 0 이상인 값을 가질 수 있는 세마포어

  • 여러 스레드가 동시에 자원에 접근할 수 있는 수를 제한

    상호배제(Mutual Exclusion)

  • 카운팅 세마포어는 카운터를 통해 특정 자원에 대한 동시 접근을 제어

    • 예를 들어 프린터가 3대 있을 때, 세마포어는 3으로 초기화되고, 최대 3개의 스레드가 동시에 프린터를 사용할 수 있음

    한정대기(Bounded Waiting)

  • 각 스레드는 제한된 시간 내에 자원을 사용할 수 있는 기회를 가져야 함

    • 5개의 스레드가 프린터를 대기 중일 때, 세마포어는 각 스레드가 무한정 대기하지 않도록 관리하여 적절한 시점에 자원을 사용하게 함

진행 상태(Progress)

  • 자원이 사용되지 않을 때, 자원을 필요로 하는 스레드 중 일부가 자원을 얻을 수 있도록 보장
    • 모든 프린터가 사용 중이지 않은 상태에서 프린터를 요청한 스레드는 즉시 자원을 사용할 수 있음

모니터(Monitor)란?

  • 객체 지향 프로그래밍에서 자원을 접근을 동기화하기 위한 구조
  • 모니터는 자원에 대한 접근을 객체화하여 한 번에 하나의 스레드만 자원에 접근할 수 있도록 함

상호배제(Mutual Exclusion)

  • 모니터는 자원에 대한 접근을 제어하여 한 번에 하나의 스레드만 자원을 사용할 수 있도록 함
    • 예를 들어 은행 계좌의 출금과 입금 작업을 모니터로 관리하여 한 번에 하나의 스레드만 계좌를 조작할 수 있게 함

한정대기(Bounded Waiting)

  • 각 스레드가 제한된 시간 내에 자원을 사용할 수 있는 기회를 가져야 함
    • 예를 들어 A 스레드가 계좌를 조작하는 동안 B와 C 스레드는 무한정 대기하지 않고 적절한 시점에 계좌를 사용할 수 있게 됨

진행 상태(Progress)

  • 자원이 사용되지 않을 때, 자원을 필요로 하는 스레드 중 일부가 자원을 얻을 수 있도록 보장함
    • 즉, 계좌를 조작하는 스레드가 없는 상태에서 자원을 요청한 스레드는 즉시 계좌를 사용할 수 있음
interface Monitor {
    void enter();
    void exit();
}

class BankAccountMonitor implements Monitor {
    private final Object lock = new Object();
    private int balance = 0;

    @Override
    public void enter() {
        synchronized (lock) {
            // 임계 구역 진입
        }
    }

    @Override
    public void exit() {
        synchronized (lock) {
            // 임계 구역 퇴장
            lock.notifyAll();
        }
    }

    public void deposit(int amount) {
        synchronized (lock) {
            balance += amount;
            System.out.println(Thread.currentThread().getName() + " deposited " + amount + ". Current balance: " + balance);
            lock.notifyAll();
        }
    }

    public void withdraw(int amount) {
        synchronized (lock) {
            while (balance < amount) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " withdrew " + amount + ". Current balance: " + balance);
        }
    }

    public static void main(String[] args) {
        BankAccountMonitor account = new BankAccountMonitor();

        Thread depositor = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                account.deposit(100);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Depositor");

        Thread withdrawer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                account.withdraw(50);
                try {
                    Thread.sleep(150);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Withdrawer");

        depositor.start();
        withdrawer.start();
    }
}

뮤텍스와 바이너리 세마포어의 차이점

  1. 메커니즘
    • 뮤텍스(Mutex) : 잠금 메커니즘(Locking Mechanism)을 사용, 자원을 사용하려는 스레드가 뮤텍스를 잠그고, 사용을 마치면 뮤텍스를 해제
    • 바이너리 세마포어(Binary Semaphore) : 신호 메커니즘(Signal Mechanism)을 사용, 세마포어를 획득하려는 스레드가 신호를 받고, 자원을 사용한 후 신호를 방출
  2. 소유권
    • 뮤텍스 : 뮤텍스를 잠근 스레드만이 뮤텍스를 해제할 수 있음
      • 즉, 소유권이 있음
    • 바이너리 세마포어 : 소유권이 없음, 세마포어를 획득한 스레드가 반드시 해제해야 하는 것은 아님(다른 스레드가 해제할 수 있음)
  3. 사용 용도
    • 뮤텍스 : 주로 단일 프로세스 내에서 스레드 간의 상호 배제를 위해 사용
    • 바이너리 세마포어 : 단일 프로세스뿐만 아니라 여러 프로세스 간의 동기화에도 사용될 수 있음

참고

이전에 작성한 동기화 자료

profile
HW + SW = 1

0개의 댓글