Semaphore & Mutex

박세건·2025년 6월 23일
post-thumbnail

배경

여러 프로세스 or 스레드가 동시에 같은 데이터를 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황
Ex. 1번스레드가 0이 들어있는 A라는 데이터를 10 증가하려는 상황


  1. 1번 스레드가 A라는 데이터를 10 증가(A는 10)
  2. ❗동시에 2번 스레드가 접근해서 A라는 데이터를 다시 10 증가(A는 20)
  3. 1번 스레드가 A라는 데이터를 확인해보니 원했던 10이 아닌 20이 되어있는 문제
  • 위 와같은 문제점을 경쟁 상태(Race Condition)라고 한다.
  • 의도하지 않았던 결과가 발생하는 문제가 발생할 수 있음

=> 이러한 문제를 해결하기 위해 공유자원에 동시에 접근하지 못하도록 접근 순서를 제어하는 방법(동기화)가 필요

  • 경쟁 상태의 데이터의 일관성을 지키기 위해 특정 프로세스/스레드만 접근할 수 있는 코드 영역을 설정 => 임계 영역(Critical Section)
    • 위와 같은 성격을 상호배제(Mutual Exclusion) 라고함

이 상호배제를 동기화 기법으로 구현한 것이 세마포어와 뮤텍스이다.

Mutex

  • 상호배제(Mutual Exclusion)의 약어
  • 여러 프로세스/스레드가 공유자원에 접근하는 것을 막기 위한 LOCK

동작 방식

  • 특정 프로세스/스레드(A)가 자원을 점유했다면 LOCK을 설정
  • 이후 다른 프로세스/스레드(B)가 자원에 접근하려고하면 LOCK이 걸려있어서 접근하지 못함
  • 접근을 기다리기 위해서 대기 큐에서 Sleep 상태로 대기
  • 이후 A의 점유가 종료되면 LOCK을 풀게되고 이후에 B가 점유

Sleep 상태로 대기하고 있는 B는 어떻게 깨워지나?

현재 자원을 소유하고 있는 A가 작업을 마치고 unlock()을 호출
이 unlock() 내부에서 대기 큐를 확인해서 대기중인 B를 깨우는 명령이 존재
이후, B는 스케줄러에 의해서 CPU를 할당받아서 Mutex를 할당 받고 실행

특징

  • Boolcan 타입의 Lock 변수 확인 가능
    • 여러 프로세스/스레드는 LOCK을 걸기 위해
  • 한개의 프로세스/스레드만 소유 및 해제
  • 점유한 프로세스/스레드가 자원에 대한 소유권과 책임을 갖음
  • 명령어
    • 소유권 획득 : Acquire or Wait
    • 소유권 해제 : Release
  • Non-Busy-Wait 방식

Non-Busy-Wait 이란?

대기하는 방법을 의미하고, 2가지 대기 방법이 존재

  • Busy-Wait
    • 원하는 자원을 얻기위해서 CPU를 사용하며 특정 조건을 만족할 때 까지 계속해서 기다림
    • Spin Lock 방식이 존재
  • Non-Busy-Wait
    • 대기 큐에서 CPU 자원을 내려놓고 대기하는 방식

Spin Lock이란?

또 하나의 동기화 기법, 자원을 얻을 때까지(lock이 풀릴 때까지) 계속 루프를 돌면서 CPU를 점유한 채 대기 하는 방식의 Lock
=> 때문에, 만약 단일 코어의 환경에서 Spin Lock을 사용하게 되면 정작 자원을 얻고 실행해야하는 프로세스/스레드가 CPU를 할당받지 못해서 무한정 대기하는 교착상태같은 상황이 발생

Semaphore

여러개의 프로세스/스레드가 공유 자원에 동시에 접근하는 것을 제한하기 위한 변수

  • Mutex가 단 하나의 프로세스/스레드만 자원에 접근이 가능했다면, Semaphore는 여러개가 접근 가능
  • 대신 접근 제한을 Semaphore 변수로 제어

동작 방식

  • Semaphore 변수값이 2인 상태
  • 특정 프로세스/스레드(A)가 자원에 접근함
  • Semaphore 변수값을 1 감소시킴(현재 Semaphore 변수값은 1)
  • 이어서 프로세스/스레드(B)가 자원에 접근
  • Semaphore 변수값을 1 감소시킴(현재 Semaphore 변수값은 0)
  • 이어서 프로세스/스레드(C)가 자원에 접근하려고함
  • Semaphore 변수값이 0이기 때문에 접근하지 못함
  • Semaphore 변수값을 -1 로 수정하고 대기 큐에서 Sleep 상태로 대기
  • A의 작업이 끝나면 Semaphore 변수값을 0 으로 증가시키고 대기큐에서 기다리는 C를 깨워서 할당함

그러면 Sempaphore 변수값이 0 인데 C를 P() 동작으로 할당할 수 있는 것인가?

자원을 할당하는 V() 메서드는 Semaphore 변수값을 1 증가시키고, 만약 대기 중인 프로세스가 있다면, 그 중 하나를 꺠워서 실행시킨다 라는 연산으로 이루어져있음
때문에, 자원을 할당 받아서 들어가는 P() 연산과는 다르게 동작함

특징

  • 세마포어 정수 사용
    • 세마포어 정수를 사용해서 음수인지 아닌지를 판별해서 할당
  • 한개 이상의 프로세스/스레드 가능
  • 자원 소유 및 책임이 없음
  • Wait(P), Signal(V)
  • Non-Busy-Wait 방식

종류

  • 이진 세마포어 (0,1) : 최대 1개의 프로세스/스레드가 접근 가능
  • 카운팅 세마포어(n) : 최대 n 개의 프로세스/스레드가 접근 가능

대기큐 선택 방식에 따른 종류

대기 큐에서 할당받는 우선순위에 따라서 종류가 달라지기도 함

  • 강성 세마포어 : FIFO 방식
  • 약성 세마포어 : 순서가 정해지지 않고 준비된 프로세스/스레드 부터 할당

비교

공통점

  • 동시에 실행되는 프로세스/스레드들 간의 공유 자원을 보호할 수 있음
  • 잘못 사용하게 되면 교착상태(DeadLock) 발생
    • 프로세스/스레드(A,B)가 있을때 A는 a를 잡고 Mutex Lock 설정 B는 b를 잡고 Mutex Lock 설정
    • 이후 A는 b를 접근하고 싶고, B는 a를 접근하고 싶어함
    • 교착상태 발생

차이점

Mutex

  • 한개의 프로세스/스레드 접근 제어
  • 락에 접근할 수 있는 것은 자원을 소유하고 있는 프로세스/스레드만 가능
    Semaphore
  • 변수
  • 한개 이상의 프로세스/스레드 접근 제어
  • 변수에 접근할 수 있는 것은 자원에 접근하려고 하는 어떤 프로세스/스레드도 Semaphore 변수에 접근 가능
  • Semaphore는 실행 순서 동기화도 가능하다
    Ex.
    • 작업 1을 진행하면서 특정 데이터의 접근을 제어하는 Semaphore 변수 값을 -1로 설정함(이루 동작 불가)
    • 작업 2가 진행되면서 위의 Semaphore 변수를 0으로 증가시키면서 풀어줌(덕분에 작업 1 이후 작업이 진행가능)
    • 작업 3은 작업 1 이후의 작업으로 작업 2가 Semaphore 변수를 해제해준 이후에 동작 가능
      => 순서를 제어할 수도 있음

때문에, 이진 세마포어와 뮤텍스는 비슷하지만 다르다.

Mutex, Semaphore 대신의 알고리즘으로 해결할 수 있지 않을까?

  • Mutex, Semaphore 이전에 알고리즘이 존재 했음
    • 데커, 피터슨, 다익스트라 알고리즘

하지만, 속도가 느리고 구현이 복잡할 뿐아니라, HW 상에서 상호배제가 깨지는 가능성이 존재.
때문에, 원자성이 지켜지는 Mutex와 Semaphore로 해결

동기화 알고리즘 간단 설명

항목데커의 알고리즘피터슨의 알고리즘
✅ 개발자데커 (Dekker)피터슨 (Peterson)
✅ 대상2개 프로세스2개 프로세스
✅ 주요 변수flag[2], turnflag[2], turn
✅ 방식상호 배제 + 번갈아 접근상호 배제 + 번갈아 접근
✅ 핵심 원리둘 다 진입 의사 → turn에 따라서 양보진입의사를 표시하고 상대방에게 turn 양보 (먼저 양보한 프로세스가 우선)
✅ 특징최초의 소프트웨어 동기화간결하고 명확
✅ Sleep 여부❌ 없음 (CPU 계속 사용)❌ 없음
✅ 스케줄러 개입없음없음

flag : 진입 의사를 나타내는 변수
turn : 자원을 점유할 프로세스를 의미

결론

  • 1개의 자원에 대한 상호 배제가 필요하다면 Mutex
  • 여러개의 프로세스/스레드의 접근이 필요하거나 작업 간의 실행순서에 대한 동기화가 필요하면 Semaphore

추가적인 동기화 기법인 모니터가 존재한다

모니터

  • 동기화를 추상화한 고수준(프로그래밍 언어 차원)의 동기화 기법
  • 모니터는 개발자가 직접 락을 걸고 해제하지 않아도, 언어 차원에서 자동으로 동기화를 관리해주는 구조

  • '공유 자원'과 그 접근을 안전하게 관리하는 코드(동기화된 메서드)를 하나의 단위로 묶은 구조

  • 언어나 시스템이 내부적으로 락을 자동 관리하므로 사용자는 더 안전하고 직관적으로 동기화할 수 있음

  • Mutex나 Semaphore보다 더 구조화되고 안전하게 Critical Section을 보호할 수 있음

Ex. Java의 Synchronized

class BankAccount {
    private int balance = 0;
    public synchronized void deposit(int amount) {
        balance += amount;
    }
    public synchronized int getBalance() {
        return balance;
    }
}

동작 원리

  • 스레드가 모니터 안의 함수(메서드)를 호출하면

  • 모니터는 자동으로 lock을 걸어줌 → 임계 영역 보호

  • 함수 실행이 끝나면 → lock이 자동으로 해제됨

  • 조건 변수를 이용해 상황에 따라 대기(wait)하거나 깨우기(notify) 가능

profile
멋있는 사람 - 일단 하자

0개의 댓글