다양한 기록

Semaphore (세마포) 본문

운영체제

Semaphore (세마포)

라구넹 2024. 4. 18. 23:02

락을 위해서도 조건변수를 위해서도 사용 가능

 

sem_init(semaphore, p_shared, initial_value)

semaphre : 사용할 세마포

p_shared : 프로세스 간 쉐어, 안하면 0으로 설정하면 됨

initial_value : 이걸 어떻게 사용할 건지에 따라 바이너리 세마포, 카운팅 세마포. 사용법이 다름

 

int sem_wait(sem_t *s) // 세마포의 값을 1 내리고 음수면 웨이트
int sem_post(sem_t *s) // 세마포의 값을 1 올리고 대기 중인 스레드 하나 꺠우기

두 함수를 사용해서 세마포 사용

 

sem_t m;
sem_init(&m, 0, 1);

sem_wait(&m);
// critical section
sem_post(&m);

락으로 사용하는 경우, 바이너리 세마포

세마포의 값을 1로 준다

 

값이 1 : 들어와도 된다. 들어오는 스레드는 값을 1 내리고 0으로 만들 것

값이 0 : 누가 수행 중이고, 기다리는 스레드는 없다. 들어오는 스레드는 값을 음수로 만들고 슬립

값이 -1, -2, -3.... : 기다리는 스레드가 절댓값만큼 있다.

 

sem_t s;

void *s child(void *arg) {
    printf("child\n");
    sem_post(&s);
    return NULL;
}

int main(int argc, char *argv[]) {
    sem_init(&s, 0, 0);
    printf("parent: begin\n");
    pthread_t c;
    pthread_create(&c, NULL, child, NULL);
    
    sem_wait(&s);
    printf("parent: end\n");
    return 0;
}

조건변수로 사용하는 경우,

세마포의 값을 0으로 줘야 함

 

부모 스레드에서는 0인 세마포의 값을 -1로 만들고 슬립

-> 자식 스레드가 끝나기 전 세마포 값읗 1 증가시켜 0으로 만들고 종료

-> 이제 세마포 값이 음수가 아니니 부모 스레드 실행 가능

 

sem_t empty;
sem_t full;
sem_t mutex;

void *producer(void *arg) {
    int i;
    for( i = 0; i < loops; i++ ) {
        sem_wait(&empty);
        sem_wait(&mutex);
        put(i);
        sem_post(&mutex);
        sem_post(&full);
    }
}

void *consumer(void *arg) {
    int i;
    for( i = 0; i < loops; i++ ) {
        sem_wait(&full);
        sem_wait(&mutex);
        put(i);
        sem_post(&mutex);
        sem_post(&empty);
    }
}

mutex -> 바이너리 세마포

full, emtpy -> 카운팅 세마포

일반 조건변수와 다르게, 조건 변수가 락 바깥에 존재

 

full은 처음엔 0으로 초기화 (버퍼가 비어 있으니까)

empty는 최대 버퍼 크기로 초기화 


Read / Writer Problem

판독자는 읽기만 하니까, 판독자 끼리는 상호배제(락)이 필요 없음

기록자 간에는 전통적인 방법으로 락하면 됨

typedef struct _rwlock_t {
    sem_t lock;			// 기록자들 락
    sem_t writelock;	// 메인 락
    int readers;
} rwlock_t;

void rwlock_acquire_readlock(rwlock_t *rw) {
    sem_wait(&rw->lock);
    rw->readers++;
    
    if( rw-> readers == 1 ) 
        sem_wait(&rw->writelock);
    
    sem_post(&rw->lock);
}

void rwlock_release_readlock(rwlock_t *rw) {
    sem_wait(&rw->lock);
    rw->readers--;
    
    if( rw-> readers == 0 ) 
        sem_post(&rw->writelock);
    
    sem_post(&rw->lock);
}

void rwlock_acquire_writelock(rwlock_t *rw) {
    sem_wait(&rw->writelock);
}

void rwlock_release_writelock(rwlock_t *rw) {
    sem_post(&rw->writelock);
}

기록자: 라이트락을 걸고, 풀기만 하면 됨

 

판독자

기록자들은 락을 잡고 카운터를 증가 -> 1이면 혼자 있는 것

혼자 있으면 기록자가 쓰고 있는지 아닌지 모르기 때문에 라이트락도 잡고 들어가야 함

 

2 이상이다 -> 먼저 들어간 기록자가 있는 것 -> 라이트락 확인할 필요가 없어짐

 

언락 시에는 반대로 카운터를 감소시키는데, 0이면 마지막으로 나가는 판독자라 라이트락을 언락해줘야 함


The Dining Philosophers

5명의 철학자, 5개의 포크 / 먹거나 생각하거나 둘 중 하나의 행동

각 철학자는 자신의 왼쪽, 오른쪽 포크 두 개를 사용해야 식사가 가능

 

포크: 공유자원

void get_forks(int p) {
    sem_wait(&forks[left(p)]);
    sem_wait(&forks[right(p)]);
}
void put_forks(int p) {
    sem_post(&forks[left(p)]);
    sem_post(&forks[right(p)]);
}

전부 자신의 왼쪽 것부터 집게 되는 상황 -> 데드락 유발

 

해결책

1. 순서 바꾸기

2. 철학자 수 제한하기

3. 트랜잭션 도입 (모니터)

4. 포크 더 쓰기

5. 포크 하나로 먹을 수 있게 만들기

 

* 트라이락: 못잡으면 undo

-> 5명이 놓고 잡고만 반복하는 라이브락 상태가 될 수 있음

 

우선순위도 무엇에 우선순위를 높게 줄 것인가 등, 어렵긴 해도 해결책은 될 것

'운영체제' 카테고리의 다른 글

I/O Device, Device Driver  (0) 2024.05.06
데드락  (0) 2024.05.06
조건 변수, 생산자/소비자 문제  (0) 2024.04.18
Lock-based Concurrent Data Structure  (0) 2024.04.18
Lock의 구현  (0) 2024.04.11