[F-Lab 멘토링 학습]

Mutex vs Monitor vs Semaphore

everydeveloper 2023. 11. 11. 16:10

Mutex vs Monitor vs Semaphore

뮤텍스(Mutex)

뮤텍스(Mutex)는 "Mutual Exclusion"(상호 배제)의 줄임말로, 다중 스레드 프로그래밍에서 한 번에 하나의 스레드만 특정 자원을 사용할 수 있도록 보장하는 메커니즘입니다.

컴퓨터에서 여러 스레드가 동시에 실행되는 환경에서는, 이러한 스레드들이 같은 자원에 접근하려 할 때 데이터의 무결성을 유지하기 위해 동기화가 필요합니다. 뮤텍스는 이러한 동기화를 위한 기본적인 도구 중 하나입니다.

뮤텍스의 작동 원리는 다음과 같습니다:

  1. 잠금(Lock): 어떤 스레드가 특정 자원을 사용하기 전에 뮤텍스를 "잠금"하여 다른 스레드가 해당 자원에 접근하지 못하게 합니다.
  2. 자원 사용: 잠금을 얻은 스레드는 필요한 작업을 수행합니다.
  3. 잠금 해제(Unlock): 작업이 끝나면 스레드는 뮤텍스를 "해제"하여 다른 스레드가 자원을 사용할 수 있도록 합니다.

뮤텍스를 사용하는 주된 이유는 "경쟁 상태"(Race Condition)를 방지하기 위해서입니다. 경쟁 상태는 여러 스레드가 동시에 같은 데이터를 수정하려고 할 때 발생할 수 있는 문제로, 예측할 수 없거나 원하지 않는 결과를 초래할 수 있습니다. 뮤텍스를 사용함으로써, 한 번에 하나의 스레드만이 자원을 조작할 수 있으므로 이러한 문제를 방지할 수 있습니다.

간단히 말해, 뮤텍스는 여러 스레드가 같은 자원을 안전하고 일관되게 사용할 수 있도록 도와주는 열쇠와 같은 역할을 합니다.

모니터(Monitor)

모니터(Monitor)는 동시성 프로그래밍에서 사용되는 동기화 구성 요소입니다. 모니터는 복수의 스레드가 동시에 코드의 특정 부분을 실행하는 것을 제한하여 자원에 대한 접근을 제어하고, 데이터의 동시성 문제를 방지하는 메커니즘을 제공합니다.

모니터의 주요 특징은 다음과 같습니다:

  1. 상호 배제(Mutual Exclusion): 모니터 안에 있는 모든 메소드는 상호 배제를 통해 실행됩니다. 즉, 한 스레드가 모니터 내의 메소드를 실행하고 있을 때는 다른 스레드들은 그 메소드 또는 다른 모니터 메소드를 실행할 수 없습니다.
  2. 조건 변수(Condition Variable): 모니터는 조건 변수를 사용하여 스레드들이 특정 조건이 충족될 때까지 대기하게 할 수 있습니다. 조건 변수를 사용하면 스레드는 특정 조건이 만족될 때까지 기다렸다가 조건이 만족되면 작업을 계속할 수 있습니다.
  3. 자동 잠금(Automatic Locking): 스레드가 모니터의 메소드를 호출하면, 그 스레드는 자동으로 잠금을 획득합니다. 메소드가 종료될 때 잠금은 자동으로 해제됩니다.

모니터를 사용하는 주된 목적은 데이터의 일관성과 무결성을 유지하면서, 여러 스레드가 공유 자원을 사용할 수 있도록 하는 것입니다. 예를 들어, 공유된 리스트에 대한 접근을 제어하고자 할 때, 리스트를 수정하는 메소드들을 모니터로 감싸서 한 번에 하나의 스레드만이 리스트에 변경을 가할 수 있도록 할 수 있습니다.

모니터는 프로그래밍 언어나 라이브러리에 따라 구현 방식이 다를 수 있지만, 기본적인 개념은 언어나 플랫폼에 관계없이 유사합니다.

Semaphore

세마포어(Semaphore)는 다중 프로세스 또는 스레드가 공유 자원에 접근하는 것을 제어하는 동기화 메커니즘입니다. 세마포어는 신호등과 유사한 개념으로, 한정된 자원에 대한 접근을 제한하고, 특정 자원을 사용할 수 있는 허용 개수를 관리합니다.

세마포어의 주요 특징은 다음과 같습니다:

  1. 카운터: 세마포어는 내부적으로 카운터를 가지고 있으며, 이 카운터는 사용 가능한 자원의 개수를 나타냅니다. 카운터의 값이 0이면 더 이상 자원을 사용할 수 없음을 의미합니다.
  2. P(Proberen) 연산: 자원을 요청할 때 사용하는 연산입니다. P 연산을 수행하면 세마포어의 카운터가 감소합니다. 카운터가 0보다 크면 자원을 얻을 수 있고, 0이면 자원을 기다려야 합니다.
  3. V(Verhogen) 연산: 자원을 반환할 때 사용하는 연산입니다. V 연산을 수행하면 세마포어의 카운터가 증가합니다. 카운터가 증가하면 대기 중인 다른 프로세스 또는 스레드가 자원을 사용할 수 있게 됩니다.

세마포어는 두 가지 유형이 있습니다:

  • 바이너리 세마포어(이진 세마포어): 0 또는 1의 값을 가지는 카운터를 가집니다. 이는 사실상 뮤텍스와 유사한 역할을 하며, 상호 배제를 위해 사용됩니다.
  • 카운팅 세마포어: 카운터가 여러 개의 자원을 관리할 수 있도록 1 이상의 값을 가질 수 있습니다. 예를 들어, 한정된 수의 데이터베이스 연결 또는 파일 핸들러 등을 관리할 때 사용할 수 있습니다.

세마포어는 특히 여러 스레드 또는 프로세스가 공유 자원을 사용해야 할 때 발생할 수 있는 동시성 문제를 해결하는 데 유용합니다. 세마포어를 통해 공유 자원에 대한 접근을 적절히 조절함으로써 경쟁 상태를 방지하고 데이터의 일관성을 유지할 수 있습니다.

Monitor 좀 더 자세히 알려줘

모니터는 동시에 실행되는 여러 스레드 사이에서 공유 자원에 대한 접근을 안전하게 관리하기 위해 사용되는 프로그래밍 개념입니다. 모니터는 상호 배제를 보장하며, 조건에 따라 스레드가 대기하고 신호를 받을 수 있는 기능을 제공합니다.

모니터의 주요 구성 요소와 작동 방식:

  1. 상호 배제(Mutual Exclusion)
    • 모니터는 자동적으로 상호 배제를 제공합니다. 즉, 한 스레드가 모니터의 메소드를 실행하고 있으면, 다른 스레드는 그 메소드 또는 모니터의 다른 메소드를 실행할 수 없습니다.
  2. 조건 변수(Condition Variables)
    • 조건 변수는 스레드가 특정 조건이 만족될 때까지 대기하게 하거나, 조건이 만족되었을 때 대기하고 있는 스레드를 깨우는 데 사용됩니다.
    • 예를 들어, 생산자-소비자 문제에서 소비자 스레드는 버퍼가 비어 있으면 데이터가 생산될 때까지 대기해야 하며, 이를 위해 조건 변수를 사용할 수 있습니다.
  3. Lock(잠금)과 Unlock(잠금 해제)
    • 모니터에 들어가는 스레드는 자동으로 잠금을 얻고, 모니터를 빠져나올 때 잠금을 해제합니다. 이는 모니터의 메소드가 호출될 때 시작되고 메소드가 종료될 때 해제되는 과정입니다.
  4. Wait(대기)과 Signal(신호) 연산
    • wait 연산은 스레드가 조건이 만족될 때까지 대기하도록 합니다. 스레드가 wait을 호출하면, 모니터는 그 스레드를 대기 상태로 전환하고 모니터의 잠금을 해제합니다.
    • signal 연산은 대기 중인 스레드 중 하나를 깨웁니다. signal이 호출되면, 시스템은 하나의 대기 중인 스레드를 선택하여 실행 상태로 전환시킵니다.

모니터 사용의 장점:

  • 데드락 방지: 모니터는 잠금과 조건 변수를 안전하게 관리하여 데드락을 방지하는데 도움을 줍니다.
  • 코드의 단순성: 모니터를 사용하면 개발자가 직접 잠금과 조건을 관리하지 않아도 되므로 코드가 단순해지고 오류 발생 가능성이 줄어듭니다.
  • 자동 잠금 관리: 모니터는 메소드 호출 시 자동으로 잠금을 관리하기 때문에 개발자가 잠금을 직접 해제하는 실수를 할 여지를 줄여줍니다.

모니터는 고급 동기화 메커니즘으로, 복잡한 동시성 문제를 해결하는데 매우 유용한 도구입니다. 하지만, 모든 프로그래밍 언어나 환경에서 지원되는 것은 아니며, 어떤 시스템에서는 모니터 대신 뮤텍스, 세마포어 등 다른 동기화 도구를 사용할 수도 있습니다.

세마포어 예제 코드

세마포어를 사용한 예제 코드는 다양한 프로그래밍 언어에서 제공하는 라이브러리나 API를 기반으로 작성될 수 있습니다. 여기서는 자바(Java)의 Semaphore 클래스를 사용한 간단한 예제를 보여드리겠습니다.

자바에서 세마포어를 사용하는 방법은 java.util.concurrent 패키지의 Semaphore 클래스를 활용하는 것입니다. 이 예제는 여러 스레드가 공유 자원에 대한 접근을 제어하는 간단한 시나리오를 보여줍니다.

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    // 세마포어 생성, 여기서는 1개의 허용(permit)을 가진 세마포어
    private static Semaphore semaphore = new Semaphore(1);

    static class SharedResource {
        public static void use() {
            try {
                // 세마포어를 획득(카운터 감소)
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + ": 공유 자원 사용 중...");
                // 공유 자원에 대한 작업 수행 - 실제 작업을 시뮬레이션하기 위한 슬립
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 세마포어를 해제(카운터 증가)
                semaphore.release();
                System.out.println(Thread.currentThread().getName() + ": 공유 자원 사용 완료.");
            }
        }
    }

    static class MyThread extends Thread {
        public void run() {
            SharedResource.use();
        }
    }

    public static void main(String[] args) {
        // 스레드를 여러 개 생성하여 공유 자원 사용 시뮬레이션
        for (int i = 0; i < 10; i++) {
            new MyThread().start();
        }
    }
}

이 코드는 Semaphore를 사용하여 여러 스레드(MyThread)가 공유 자원(SharedResource)을 사용할 때 서로 충돌하지 않도록 관리합니다. 세마포어는 acquire() 메소드로 획득되고, release() 메소드로 해제됩니다. 세마포어의 카운터가 0이 되면, 다른 스레드는 acquire() 메소드가 세마포어를 해제할 때까지 즉, release() 메소드가 호출될 때까지 대기해야 합니다.

위 코드에서는 세마포어의 허용(permit) 수를 1로 설정하여 한 번에 하나의 스레드만이 공유 자원을 사용할 수 있도록 하고 있습니다. 이는 사실상 뮤텍스와 같은 역할을 합니다. 만약 동시에 여러 스레드가 자원을 사용해야 하는 경우, 세마포어의 허용 수를 늘리면 됩니다.

뮤텍스와 모니터 자바 예제 코드

Java에서 뮤텍스(mutex)와 모니터(monitor)는 일반적으로 synchronized 키워드를 사용하여 구현됩니다. 뮤텍스는 특정 객체에 대한 접근을 동기화하여, 한 번에 하나의 스레드만이 그 객체를 사용할 수 있게 합니다. 모니터는 이보다 더 고급의 개념으로, 객체의 메소드를 동기화하고, 조건 변수를 통해 스레드 간 통신을 가능하게 합니다.

아래에 Java에서 뮤텍스와 모니터를 사용하는 예제 코드를 제공하겠습니다.

뮤텍스 사용 예제 (Java synchronized):

public class MutexExample {
    // 공유 객체
    public static class Shared {
        // synchronized 메소드로 뮤텍스 구현
        public synchronized void safeMethod() {
            System.out.println(Thread.currentThread().getName() + " is entering safeMethod");
            try {
                // 실제 작업을 시뮬레이션하기 위한 슬립
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " is leaving safeMethod");
        }
    }

    public static void main(String[] args) {
        // 공유 객체 인스턴스화
        Shared sharedResource = new Shared();

        // 스레드를 여러 개 생성하여 공유 객체의 메소드 호출
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                sharedResource.safeMethod();
            });
            thread.start();
        }
    }
}

모니터 사용 예제 (Java synchronized 및 wait/notify):

public class MonitorExample {
    // 공유 객체
    public static class Shared {
        private boolean isAvailable = false;

        // synchronized 메소드로 모니터 구현
        public synchronized void waitForAvailability() {
            while (!isAvailable) {
                try {
                    // 조건 변수를 사용하여 대기
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 리소스를 사용할 수 있게 되었을 때의 작업 수행
            System.out.println(Thread.currentThread().getName() + " is using the resource");
            isAvailable = false; // 리소스 상태 변경
        }

        public synchronized void makeAvailable() {
            // 리소스를 사용할 수 있는 상태로 만듬
            isAvailable = true;
            // 대기하고 있는 스레드 중 하나를 깨움
            notify();
        }
    }

    public static void main(String[] args) {
        Shared sharedResource = new Shared();

        // 리소스를 사용하려고 시도하는 스레드
        Thread t1 = new Thread(() -> {
            sharedResource.waitForAvailability();
        });

        // 리소스를 사용 가능하게 하는 스레드
        Thread t2 = new Thread(() -> {
            sharedResource.makeAvailable();
        });

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

위의 MonitorExample에서 waitForAvailability 메소드는 조건 변수를 사용하여 isAvailable이 true가 될 때까지 스레드가 대기하도록 합니다. makeAvailable 메소드는 이 조건을 만족시켜 대기 중인 스레드를 깨우는 역할을 합니다. synchronized 블록은 메소드 전체 또는 특정 코드 블록을 동기화할 때 사용되며, wait와 notify는 스레드 간의 통신을 위해 사용됩니다.

Java에서는 synchronized 키워드를 사용하여 객체의 메소드나 코드 블록에 대한 접근을 동기화할 수 있고, 이러한 방식으로 뮤텍스와 모니터 기능을 구현할 수 있습니다.

세마포어는 동시성 제어랑 관련이 깊어 보이는데

세마포어는 동시성 제어에 매우 중요한 역할을 합니다. 동시성 제어는 여러 스레드나 프로세스가 자원을 공유할 때 발생할 수 있는 충돌이나 일관성 없는 데이터 문제를 방지하는 방법론입니다. 이러한 문제들을 해결하기 위해, 세마포어는 특정 자원에 대한 접근을 조정하는 데 사용되는 변수입니다.

세마포어는 사실상 두 가지 기본 연산, 즉 acquire (또는 P 연산)과 release (또는 V 연산)으로 구성됩니다. acquire 연산은 자원을 사용하고자 할 때 호출되며, 세마포어의 값을 감소시킵니다. 만약 세마포어의 값이 0이라면, 해당 연산을 호출한 스레드는 세마포어의 값이 0보다 커질 때까지 대기하게 됩니다. 이는 해당 자원이 사용 중임을 의미하고, 다른 스레드가 자원을 사용하고 있으니 대기해야 함을 알립니다.

반면, release 연산은 자원의 사용이 끝났을 때 호출되며, 세마포어의 값을 증가시킵니다. 이는 다른 대기 중인 스레드에게 자원이 사용 가능함을 알려주고, 대기 중인 스레드 중 하나가 자원을 사용할 수 있게 됩니다.

이러한 세마포어의 동작은 '카운팅 세마포어'와 '바이너리 세마포어' 두 종류에서 나타납니다. 카운팅 세마포어는 다수의 허용 가능한 자원에 대해 사용되며, 세마포어의 값은 사용 가능한 자원의 수를 나타냅니다. 바이너리 세마포어는 값이 0 또는 1만을 가질 수 있으며, 뮤텍스와 유사한 방식으로 동작하여 오직 하나의 스레드만이 자원을 사용할 수 있도록 합니다.

세마포어는 따라서 동시성 프로그래밍에서 교착 상태(deadlock), 기아 상태(starvation), 경쟁 조건(race condition) 등의 문제를 예방하고 해결하는 데 핵심적인 동기화 도구로 사용됩니다. 이를 통해 소프트웨어의 안정성과 예측 가능성을 향상시킬 수 있습니다.

뮤텍스와 모니터 자바 좀 더 자세히

자바에서 뮤텍스(mutex)와 모니터(monitor)는 동시성을 다루는 중요한 개념입니다. 자바에서는 이들을 구현하기 위해 주로 synchronized 키워드, ReentrantLock 클래스, wait, notify, notifyAll 메소드 등을 사용합니다. 여기서 뮤텍스와 모니터의 개념과 자바에서의 구현에 대해 자세히 설명하겠습니다.

뮤텍스 (Mutex) in Java

뮤텍스는 한 번에 하나의 스레드만 특정 자원이나 코드 블록에 접근할 수 있도록 하는 도구입니다. 자바에서 뮤텍스는 주로 synchronized 키워드와 ReentrantLock 클래스를 사용하여 구현할 수 있습니다.

synchronized 사용 예제:

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

    public void safeMethod() {
        synchronized (lock) {
            // 공유 자원에 대한 안전한 접근
            // ...
        }
    }
}

위 예제에서 safeMethod 메소드 내부의 코드 블록은 synchronized 블록으로 감싸져 있으며, 이는 한 번에 하나의 스레드만 이 블록을 실행할 수 있도록 합니다.

ReentrantLock 사용 예제:

import java.util.concurrent.locks.ReentrantLock;

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

    public void safeMethod() {
        lock.lock();
        try {
            // 공유 자원에 대한 안전한 접근
            // ...
        } finally {
            lock.unlock();
        }
    }
}

ReentrantLock을 사용한 경우, lock() 메소드로 잠금을 획득하고 unlock() 메소드로 잠금을 해제합니다.

모니터 (Monitor) in Java

모니터는 뮤텍스를 기반으로 하지만, 조건에 따른 스레드 대기와 깨우기 기능을 추가로 제공합니다. 이를 위해 자바는 wait, notify, notifyAll 메소드를 제공합니다.

모니터 사용 예제:

public class MonitorExample {
    public synchronized void waitForCondition() throws InterruptedException {
        while (/* 조건이 충족되지 않았을 때 */) {
            wait(); // 조건이 만족될 때까지 대기
        }
        // 조건이 충족됐을 때 수행할 작업
    }

    public synchronized void changeCondition() {
        // 조건 변경
        notify(); // 대기 중인 스레드 중 하나를 깨움
        // or
        // notifyAll(); // 모든 대기 중인 스레드를 깨움
    }
}

waitForCondition 메소드는 조건이 충족될 때까지 스레드가 대기하도록 하며, changeCondition 메소드에서 조건을 변경하고 대기 중인 스레드를 깨우는 역할을 합니다.

자바에서의 뮤텍스와 모니터는 멀티 스레드 환경에서 자원 공유를 안전하게 관리하고, 동시성 관련 문제를 예방하는 데 매우 중요합니다.

'[F-Lab 멘토링 학습]' 카테고리의 다른 글

HTTPS 암호화 작동원리-2  (0) 2023.11.11
TCP와 HTTP의 차이, OSI 7계층  (1) 2023.11.11
Public IP vs Private IP  (0) 2023.11.11
프로세스와 스레드의 차이  (0) 2023.11.11
Call-by-value vs Call-by-reference  (0) 2023.11.11