컴퓨터 사이언스

세마포어

woohap 2024. 10. 24. 03:01

세마포어

공유 자원에 대한 접근을 조절하기 위한 동기화 도구 
세마포어는 초기값에 의해 동작이 결정되므로 가장 먼저 초기화를 해야한다.
세마포어는 정수 값을 갖는 객체로서 두 개의 함수로 조작 
POSIX 표준의 두 개 함수 // 두 함수는 원자적으로 실행 

1. sem_wait()
2. sem_post()
#include<semaphore.h>
sem_t s;
sem_init(&s, 0, 1);

세마포어 초기화

세마포어 s를 선언한 후, 세 번째 인자로 1을 전달하여 세마포어 값을 1로 초기화한다.
두 번째 인자는 OSTEP의 모든 예제에서 0 
이 값이 0인 경우 같은 프로세스 내의 쓰레드 간에 세마포어를 공유한다는 것을 의미 

** 초기화 후 sem_wait(), sem_post() 함수들을 호출하여 세마포어를 다룰 수 있다. 
두 루틴은 다수 쓰레드들에 의해 동시에 호출되는 것을 가정

sem_wait()

1. 세마포어 값이 1인 경우, 즉시 리턴
2. 세마포어 값이 1 이상이 될 때까지 호출자를 대기 

다수의 쓰레드들이 sem_wait()를 호출할 수 있기 때문에 대기큐에는 다수의 쓰레드가 존재할 수 있다. 

대기하는 법에는 회전과 재우기 두 가지가 있다.

sem_post()

세마포어 값을 증가시키고 대기 중인 쓰레드 중 하나를 깨운다. 

세마포어 값 음수

세마포어 음수값의 절대값은 현재 대기 중인 쓰레드의 개수와 같다 

Ex) -2인 경우 2개의 스레드가 대기 중임

이진 세마포어 - 세마포어를 락처럼 사용

락은 두 개의 상태만 존재하므로 이진 세마포어라고도 불린다.
세마포어 값을 1로 초기화

스레드 0이 sem_wait()를 호출하여 세마포어 값을 0으로 만들고 리턴 
스레드 0은 임계 영역에 진입할 수 있음
스레드 0이 임계 영역 내에 있을 때 다른 스레드가 락을 획득하려고 하지 않는다면 
sem_post()를 불렀을 때 세마포어 값이 다시 1로 설정된다. 

스레드 0이 임계 영역 내에 있을 때 스레드 1이 sem_wait()를 호출하여 
임계 영역 진입을 시도하는 경우 스레드 1이 세마포어를 -1로 감소시키고 대기에 들어간다. 
(프로세서를 내어주고 잠자기에 들어감)

스레드 0가 다시 실행되고 sem_post()를 호출하면, 세마포어 값을 0으로 증가시키고, 
잠자던 스레드 1을 깨운다. 그러면 스레드 1이 락을 획득할 수 있게 된다. 
이후 스레드 1이 작업을 마치면 sem_post()를 호출하여 세마포어 값을 증가시킨다.

컨디션 변수로서의 세마포어

어떤 조건이 참이 되기를 기다리기 위해 현재 쓰레드를 멈출 때에도 세마포어는 유용하게 사용됨 
쓰레드가 어떤 사건의 발생을 기다리고, 또 다른 쓰레드는 해당 사건을 발생시킨 후, 
시그널을 보내어 기다리는 쓰레드를 깨우는 것 
대기 중인 쓰레드가 프로그램에서 어떤 조건이 만족되기를 대기하므로 
세마포어를 컨디션 변수처럼 사용할 수 있다. 

** 세마포어 값 0으로 초기화
sem_t s;

void *
child(void *arg) {
    printf("child\n");
    sem_post(&s);  // 시그널 전달: 자식의 동작이 끝남 
    return NULL;
}

int 
main(int argc, char *argv[]) {
    sem_init(&s, 0, X); // X의 값은 무엇이 되어야 할까 ?
    printf("parent: begin\n");
    pthread_t c;
    pthread_create(c, NULL, child, NULL);
    sem_wait(&s);     // 자식 여기서 대기 
    printf("parent: end\n");
    return 0;
}

어떻게 컨디션 변수로서의 세마포어 효과를 낼 수 있을까 ??

부모 프로세스는 자식 프로세스를 생성 후 sem_wait()를 호출하여 자식의 종료를 대기한다.
자식 프로세스는 sem_post()를 호출하여 종료되었음을 알린다. 
그러므로 세마포어의 초기값이 0으로 초기화되어야 한다.

첫 번째 상황
두 번째 상황

두 가지 상황 발생

1. 자식 프로세스 생성 후, 아직 자식 프로세스가 실행을 시작하지 않은 경우 (준비 큐에만 있는 상태)
    이 경우 자식이 sem_post()를 호출하기 전에 부모가 sem_wait()를 호출하게 되므로 
    부모 프로세스는 자식 프로세스가 실행될 때까지 대기해야 한다. 
    이를 위해 wait() 호출 전에 세마포어 값이 0보다 같거나 작아야 한다. 

    1) 부모가 실행되면 세마포어 값을 감소시키고(-1) 대기한다.

    2) 자식이 실행되었을 때 sem_post()를 호출하여 세마포어 값을 0으로 증가시킨 후 부모를 깨움

        그러면 부모는 sem_wait()에서 리턴하여 프로그램을 종료시킨다.

2. 부모 프로세스가 sem_wait()를 호출하기 전에 자식 프로세스의 실행이 종료된 경우 
    이 경우 자식이 먼저 sem_post()를 호출하여 세마포어 값을 0에서 1로 증가시킨다.
    부모가 실행할 수 있는 상황이 되면 sem_wait()를 호출한다. 
    부모는 세마포어 값이 1인 것을 발견하고 세마포어 값을 -으로 감소시키고 sem_wait()에서 대기 없이 리턴한다.

계수 세마포어

초기값을 1보다 큰 정수로 설정하여 동일한 종류의 여러 자원을 관리
초기값은 관리할 수 있는 총 자원 수를 정의 
자원을 사용할 때마다 세마포어 값 감소, 반환할 때마다 증가 
Ex)
5대의 프린트 관리, 세마포어 초기값 5

** 
계수 세마포어를 사용하는 이유 
  1. 하나의 세마포어로 동일한 유형의 자원을 관리할 수 있음 
  2. 부하를 분산 및 리소스 사용 최적화 (여러 큐로 관리하면 하나의 큐에 작업이 몰릴 수 있음)
  3. 구현 단순화 
  4. 확장성 좋음 // 초기 세마포어 값만 수정하면 됨 
  5. 프로세스간 동기화 같은 추가적인 기능 구현이 쉬움 
  같은 자원에 동시에 접근하는 것을 막을 수 있기 때문 
  6. 데드락 방지
  설계가 잘 된 경우 모든 프로세스가 동일한 순서로 자원을 요청하고 해제할 수 있음 

**
계수 세마포어는 서로 다른 자원을 관리하는데 사용할 수 있음
단, 복잡성이 증가시킬 수 있어 일반적인 사용 사례는 아님

'컴퓨터 사이언스' 카테고리의 다른 글

교착상태  (0) 2024.10.26
경쟁상태(Race Condition)  (0) 2024.10.25
임계영역, 공유자원, Lock, Mutex  (0) 2024.10.23
프로세스와 스레드  (0) 2024.10.22
CDN  (1) 2024.10.20