OS 공부 - Bounded-Buffer Problem, Readers-Writers Problem

정휘제·2024년 1월 27일

OS 공부

목록 보기
6/8
post-thumbnail

Concurrency-Contrl Problem
동시성 제어 문제들

Bounded-Buffer 문제
Producer-Consumer 문제
Readers-Writers 문제
Dining-Philosopher 문제

Bouded-Buffer Problem

P와 Q는 중간에 공유된 Buffer (shared data) 를통해 주고받는다
n개의 버퍼가 있다. 버퍼의사이즈는 n , 각각의 버퍼에 한개의 아이템보유할 수 있다.
producer 는 버퍼를 가득채우는게 목표, consumer는 producer가 버퍼를 쓸 수 있게 비워주는게 목표

Shared Data Structures

int n;
semaphore mutex = 1;  -> binary 세마포어

semaphore empty = n;
semaphore full = 0

mutex 세마포어 - buffer pool에 엑세스할 때 상호배제해라 , mutex = 1
버퍼 공간에 접근하는 동안 다른 프로세스가 접근하지 못하게 상호 배제를 제공
mutex = 1 초기화, mutex = 0 : 접근 불가능, mutex = 1 : 접근 가능함

empty : 비어있는 버퍼의 개수를 의미하는 세마포어
empty = 0 : 버퍼가 가득찼다는 의미
empty = n : 버퍼가 비어있다는 의미

full : 차있는 버퍼의 개수를 의미하는 세마포어
full = 0 : 버퍼가 비어있다는 의미
full = n : 버퍼가 가득찼다는 의미

counting semaphores - empty, full
empty 버퍼, full 버퍼를 카운팅하기 위해서 사용
empty 는 n으로 초기화하고 점점 감소 --
full 은 0으로 초기화 하고 점점 증가 ++

puducer

while (true) {
	/* produce an item in next_produced */
    
    wait(empty);  // 소비자가 empty 될때까지 기다리자
    wait(mutex);  // mutex를 가질때까지 기다리자
    
    // critical section
    /* add next_produced to the buffer */
    
    signal(mutex)  // mutex 반납 
    signal(full);  // consumer에게 가져가라고 알려줌
}

empty의 lock은 여러 생산자 프로세스가 가질 수 있음
그러나 mutex lock은 이진 세마포어이기 때문에 버퍼에 접근할 수 있는 프로세스는 단 하나 뿐임.
empty = 0 : 버퍼가 가득찼다는 의미이므로 생산자 프로세스는 대기해야 한다.

Consumer

while (true) {
	wait(full);  // consumer가 signal 줄때만 깨어남
    wait(mutex);
    
    // critical section
    /* remove an item from buffer to next_consumed */
    
    signal(mutex);
    signal(empty);
    
    /* consume the item in next_consumed */

full lock을 가질수 있는 프로세스는 여러개이다.
그러나 mutex lock은 이진 세마포어이기 때문에 버퍼에 접근할 수 있는 프로세스는 단 하나 뿐이다.
full = 0 : 버퍼가 비어있다는 의미이므로 소비자 프로세스는 대기한다.

양쪽 대칭구조가 잘 맞아야함 , wait(empty)와 wait(mutex) 순서 바껴있거나, signal(mutex) signal(empty) 순서 바껴있거나하면 꼬인다.

The Readers-Writers Problem

자기 프로세스들이 concurrently 하게 돌면서 어떤 프로세스는 읽기만한다, 어떤 프로세스들은 읽고 쓰고 한다
concurrent process들이 database 엑세스할때
select from 만 열심히하는 프로세스 : reader
update 하는 프로세스들 : writer

두개 이상의 reader 들이 동시에 shared data 에 접근한다면-> 문제 안생김
데이터 무결성을 해치는일은 안함

writer 가 다른 writer하고 같이 shared data에 접근하면 , reader와 writer가 같이 엑세스하면 -> 데이터 무결성 깨짐

Readers-Writers Problem 유형
우선순위 관련
first readers-writers problem:
Reader 프로세스가 임계 영역에서 데이터를 읽는 상태에서 Writer 프로세스가 대기하고 있다고 해서 Reader 프로세스가 대기해야할 필요는 없다.
-> reader 들이 너무많아서 writer 가 접근못한다. starvation 발생한다.
공유된 데이터를 reader들과, writer 들이 대기중이면 같은 우선순위로 공평하게 기회를줘서 lock 획득한놈이 엑세스 하도록 해야함

second readers-writers problem:
writer 가 엑세스하려고 기다리면 , 새 reader 들은 진입하지 못한다.
-> write 한다고 reader 들이 접근하지 못한다. starvation 발생한다

-> 둘 다 starvation이 발생할 수 있다.

first readers-writers ploblem 해결법

semaphore rw_mutex = 1;  // 1 초기화 binary semaphore
						 // critical section 보호 
semaphore mutex = 1;    // 1 초기화 binary semaphore
						// critical section 보호 
int read_count = 0;     // 현재의 read 갯수가 0이되면 write 접근할 수있다. 

mutex : read_count를 갱신할때 다른 Reader 프로세스의 접근을 막기 위한 상호 배제에 사용되는 lock
rw_mutex : 다른 writer의 접근을 막기 위한 상호 배제에 사용됨
Reader 프로세스와 Writer 프로세스가 둘다 참조한다.
read_count : 현재 몇 개의 Reader 프로세스들이 객체를 읽고 있는지 알려줌
read_count = 0 : 공유 자원을 읽고 있는 Reader 프로세스들이 없으므로 Writer 프로세스가 진입해도 된다는 의미

rw_mutex를 reader와 writer 공유해서 쓰고
mutex를 read_count가 update될때 mutual exclusion 보장하는데 쓴다
read_count는 얼마나 많은 프로세스를 읽고 있는지 체크, read count가 0이되면 writer 접근할수있다.

writer process

while(true)
	wait(rw_mutex);  // rw_mutex획득하면 진입
    
    /* writing is perforemd */
    
    signal(rw_mutex);  // 끝나고나서 시그널 줘라
}

reader process

while(true){
	---
	wait(mutex);			// 다른 Reader 프로세스들과 lock을 얻기 위해 경쟁
    read_count++;			//reader가 들어가기전에 count++
    
    // 만약 현재 읽을려고 하는 Reader 프로세스가 첫번째라면
    // rw_mutex의 lock을 얻는다. 그러면 다른 Writer 프로세스는
    // 대기해야 한다.
    
    if (read_count ==1)		// count가 1이면 wait
    	wait(rw_mutex);
    signal(mutex);
    --- (critical section) 
    
    /* reading is performed */
    
    ---
    wait(mutex);
    read_count--;			//읽고나서 count -- 
    if (read_count == 0)    //raed_count가 0이면 signal
    	signal(rw_mutex);   // writer의 rw_mutex 깨움
        					// reader 도 여기서 깨움
    signal(mutex);          
    --- (critical section) 
}

Writer 프로세스가 임계 영역에서 작업중이면 n개의 Reader 프로세스는 대기중
n개의 Reader중에서 1개의 Reader 프로세스는 rw_mutex의 큐에 있습니다.
1개의 Reader 프로세는 제일 첫번째로 읽을려고 하는 Reader 프로세스입니다.
n개의 Reader중에서 n-1개의 Reader 프로세스는 mutex의 큐에 있습니다.
Writer 프로세스가 signal(rw_mutex)를 실행하는 경우
대기중인 Reader 프로세스들 또는 단일 대기중인 Writer 프로세스가 실행을 재개한다.
누구를 선택할 것인지는 스케줄러에 달려 있다.

writer 한명은 critical section에 들어갈 수 있는데, n개의 reader들은 기다려야한다.
한명의 reader가 rw_mutex 쥐고있으면 이거 제외한 n-1개의 reader들은 mutex에서 대기하고 있다.
signal(rw_mutex)를 해줘야 그때 resume 시킬 수 있는데 스케줄러가 reader , writer 공평하게 준다. => first 해결방법
writer 에게 먼저 기회를 준다 => second 해결방법

The Reader-Writer Locks

read 모드냐 write 모드냐 실행해준다.
여러개의 프로세들은 읽기 모드에서 Reader-Writer lock을 얻을 수 있다.
그러나 오직 하나의 프로세스만이 Writer 프로세스들을 위해 요구되는 배타적인 접근으로서 쓰기에 대한 lock을 얻을 수 있다.

여러개의 프로세스가 read모드에서 reader-writer lock을 획득하면 여러개가 진입할 수 있고, righting lock을 획득하면 한개만 진입하게 한다.
-> exclusive access가 writers에서 보장됨
리더프로세스와 라이터프로세스가 서로 공유되어있는 동기화 문제를 품

Bounded Buffer Problem

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#define true 1
#define BUFFER_SIZE 5  // 버퍼사이즈 5

int buffer[BUFFER_SIZE]; // producer, consumer 공유하는 버퍼

pthread_mutex_t mutex; // mutex
sem_t empty, full;    // empty, full 세마포어

int in = 0, out = 0;  // in, out 
   
int main(int argc, char *argv[]) {
	int i, numOfProducers = 1, numOfConsumers = 1;
    // producer 1개, consumer 1개
	pthread_t tid;
    
	pthread_mutex_init(&mutex, NULL);  // mutex초기화
	sem_init(&empty, 0, BUFFER_SIZE);  // empty 버퍼사이즈 5 초기화
	sem_init(&full, 0, 0);             // full 0초기화
	srand(time(0));
	// Create the producers
	for (i = 0; i < numOfProducers; i++)  // number 1 만큼 생성
		pthread_create(&tid, NULL, producer, NULL);
	// Create the consumers
	for (i = 0; i < numOfConsumers; i++)  // 1개의 컨슈머 생성
		pthread_create(&tid, NULL, consumer, NULL);
        
	sleep(10);
	return 0;
}
void *producer(void *param) {
	int item;
	while (true) {
		usleep((1 + rand() % 5) * 100000);
		item = 1000 + rand() % 1000;
		insert_item(item); // critical section
        //item 을 버퍼에 넣자
	}
}

void *consumer(void *param) {
	int item;
	while (true) {
		usleep((1 + rand() % 5) * 100000);
		remove_item(&item); // critical section
        
	}
}
void insert_item(int item) {
	// remove와 티키타가
	sem_wait(&empty);      // semaphore 획득
	pthread_mutex_lock(&mutex);   // mutex 락 획득
    
	buffer[in] = item;   
	in = (in + 1) % BUFFER_SIZE;     // in을 증가시켜준다 
	printf("Producer: inserted $%d\n", item);
    
	pthread_mutex_unlock(&mutex);  // 나오때 free
	sem_post(&full);   // signal(full) 
}

void remove_item(int *item) {
	sem_wait(&full);
	pthread_mutex_lock(&mutex);
    
	*item = buffer[out];
	out = (out + 1) % BUFFER_SIZE;  // out을 증가시켜준다. 
	printf("Consumer: removed $%d\n", *item);
    
	pthread_mutex_unlock(&mutex);
	sem_post(&empty);
}

버퍼가 한개일때, n개일때 producer가 한개일때, 여러개일때, consumer 한개일때, 여러개일때 등.. 동기화문제 생기지 않음
=> semaphore을 이용한 기본적인 concurency control

profile
개발자

0개의 댓글