[OS]뮤텍스(Mutex)와 세마포어(Semaphore)

두지·2023년 3월 31일
0
post-thumbnail

만약, 동시에 여러 스레드가 공유 자원에 접근하면 어떻게 될까? 아마 race condition에 놓여 프로그램의 안정성과 일관성을 해치게 될 것이다.

다음과 같은 문제가 발생할 수 있다.

1.일관성 없는 데이터 : 두 개 이상의 스레드가 동시에 공유 자원을 변경하거나 읽을 때 서로 다른 데이터를 읽게되어 다른 정보를 제공 받을 수 있다.

2.데드락(deadlock) : 두 개 이상의 스레드가 서로 다른 자원을 점유하고, 서로 상대방의 자원을 기다릴 때 교착상태가 발생할 수 있다.

3.성능 저하 : 스레드간의 경쟁 상황으로인해 성능이 저하된다.

이러한 상황을 방지하기 위해서 동기화 기법인 뮤텍스나 세마포어를 사용하여 하나의 스레드만이 공유자원에 접근할 수 있도록 제한한다.

그럼 이 둘의 차이가 없는거 아냐?

뮤텍스의 의미와 세마포어의 의미를 알아보자!

잠깐! 동기화와 비동기화를 알고 있어야하기 때문에 모른다면 아래 글을 읽고 오자.

[OS]동기화와 비동기 차이가 뭐야!?

뮤텍스(Mutex)

Mutual Exclusion의 합성어로 . 공유된 자원의 데이터나 임계영역(critical section) 같은 곳에 스레들의 running time이 서로 겹치지 않게 하나의 프로세스 또는 스레드가 접근하는 것을 막는다.

뮤텍스는 상호배제를 구현하기 위한 동기화 기법중 하나인데, 공유 자원에 대한 접근을 동시에 하나의 스레드만 가능하게 제한한다. 즉, 뮤텍스는 동기화 대상이 하나인게 특징이다. 다른 스레들은 뮤텍스의 lock을 가지기 위해 대기하며. 락을 해제하는 스레드가 있을 때까지 다른 스레드를은 접근하지 못한다. 한 프로세스에 의해 소유될 수 있는 key 기반으로 한 상호배제 기법이고 key에 해당하는 객체가 있으며 이 객체를 소유한 스레드와 프로세스만이 공유자원에 접근할 수 있다. 다중 프로세스들의 공유 리소스에 대한 접근을 조율하기위해 lock을 사용함으로써 뮤텍스 객체를 두 스레드가 동시에 사용할 수 없다. DB에서는 2PL(2phase Locking)과 비슷한 방법이다.

아래는 자바로 뮤텍스를 구현하였다.

뮤텍스 구현

mutex = 1;

void lock () {
	while (mutex != 1) {
    	/* mutex 값이 1이 될 때까지 기다린다.*/
    }
    /* 이 구역에 도착했다는 것은 mutex 값이 1이라는 것이다.
       따라서 이제 뮤텍스 값을 0으로 만들어 다른 프로세스(혹은 쓰레드)가 접근하지 못하도록 막아야한다.
    */
    mutex = 0;
}

void unlock() {
	/* 임계 구역에서 나온 프로세스는 다른 프로세스가 접근할 수 있도록 락을 해제한다.*/
	mutex = 1;
}

세마포어(Semaphore)

세마포어는 리소스의 상태를 나타내는 간단한 카운터로 생각할 수 있다. 일반적으로 비교적 긴 시간을 확보하려는 리소스에 대해 이용하게 되며, 유닉스 시스템의 프로그래밍에서 세마포어는 운영체제의 리소스를 경쟁적으로 사용하는 다중 프로세스에서 행동을 조정하거나 동기화 시키는 기술이다.

####세마포어 도식화

  1. 스레드 A가 먼저 실행이 된다면 acquire()가 실행되고 value는 0이 된다.
  2. 0보다 작지 않기에 acquire()내부 if문을 만족하지 않고 block 되지 않는다. 트랜젝션이 실행된다.
  3. 트랜젝션을 update 하는 중 context switching이 발생하여 스레드 B가 돈다.
  4. B는 acquire()을 실행하고 value는 -1이 된다.
  5. 내부 if문을 만족했기에 block처리 되고, Queue에 갇힌다.
  6. 다시 순서가 돌아와서 스레드 A가 실행이 되고 release()를 호출해 value는 다시 0이 된다.
  7. +1을 했음에도 0이 되었다는 것은 Queue에 대기중인 프로세스가 있다는 뜻이기에 B를 다시 Ready Queue로 보내고 cpu에 의해 실행되기를 기다리다 동일하게 순차적으로 실행이 된다.

세마포어는 초기에 P, V로 사용되었으며 P는 test이며, V는 increment이다. (왜 세마포어라는 이름일까 찾아보니 역이나 군대에서 사용하던 수신호였다고 한다.) 사용하고 있는 스레드와 프로세스의 수를 공통으로 관리하는 하나의 값을 이용해 상호배제를 달성한다. 공유 자원에 접근할 수 있는 프로세스의 최대 허용치만큼 스레드가 접근할 수 있으며, 각 프로세스는 세마포어 값을 확인하고 변경할 수 있다. 자원을 사용하지 않는 대기 상태가 될 때 대기하던 프로세스가 즉시 자원을 사용하고, 이미 다른 프로세가 사용중이라는 사실을 알면 일정 시간 대기후 재시도 한다.

세마포어 초기 구현 방식

acquire(S) {
     while S <=0; // 아무것도 하지 않음 (반복문)
     S--;
 }

 release(S) { 
     S++;
 }
 

최초 제시된 세마포어 구현은 busy waiting을 이용한 방법이다. 이 방법은 critical section에 접근할수 있을 때까지 빈 반복문을 수행하기 때문에, 단일처리기 다중프로세스 환경에서 처리기의 효율이 떨어진다. 또한 대기 중의 프로세스들 중 어느 프로세스를 먼저 critical section에 접근시킬지를 결정할 수 없다.

이러한 단점을 보완한 방법으로 waiting queue(재움 큐)를 활용한(block-wakeup)방식이 있다.
아래는 자바로 구현하였다.

Semaphore(block-waiting)

class Semaphore {
  int value;      // number of permits
  Semaphore(int value) {
    // ...
  }
  void acquire() {
    value--;
    if (value < 0) {
      // add this process/thread to list
      // block
    }
  }
  void release() {
    value++;
    if (value <= 0) {
      // remove a process P from list
      // wakeup P
    }
  }
}

acquire() 는 value값을 감소시키는 역할, 만약 value값이 0보다 작으면 이미 그 임계구역에 프로세스가 존재한다는 의미이므로 현재 프로세스는 접근하지 못하도록 막아준다. 그리고 list 대기줄에 추가한 뒤 실행되지 못하도록 block을 건다.

release() 는 value의 값을 증가시는 역할, 만약 value가 0보다 같거나 작으면 임계구역에 진입하려고 대기하는 프로세스가 list에 남아있다는 의미이므로 그 중에서 하나를 꺼내어 임계구역을 수행할 수 있도록 한다.

차이점

  • 동기화 대상의 갯수 차이 : 뮤텍스는 동기화 대상이 오직 하나이고, 세마포어는 변수만큼의 프로세스 접근이 가능하여 하나 이상이다.
  • 세마포어는 lock을 소유할 수 없지만 뮤텍스는 책임을 가지는 대신 lock을 소유를 할 수 있다. 그 이유는 뮤텍스 의 상태는 0,1 이 두개 뿐이기 때문이다.

네, 자바에서는 java.util.concurrent.Semaphore 클래스를 사용하여 세마포어를 구현할 수 있습니다. Semaphore은 일정한 개수의 스레드가 동시에 접근할 수 있는 리소스의 개수를 제어하는 데 사용됩니다. Semaphore 객체를 생성하면서 지정된 개수만큼의 스레드가 동시에 접근할 수 있도록 허용합니다. acquire() 메소드를 호출하면 세마포어가 잠금 상태가 되어 다른 스레드가 접근할 수 없게 됩니다. 이후 리소스를 사용한 후 release() 메소드를 호출하여 세마포어를 잠금 해제하면 다른 스레드가 리소스에 접근할 수 있게 됩니다. 이를 통해 스레드 간의 상호배제와 동기화를 구현할 수 있습니다.

[참고 문헌]

https://gona.tistory.com/71#comment13528023
https://gona.tistory.com/48

profile
인생은 끝이 없는 도전의 연속입니다. 저는 끝 없이 함께 새로운 도전을 합니다.

0개의 댓글