지난 글에서는 세마포어의 정의와, '상호배제 문제'와 '동기화 문제'를 세마포어가 어떻게 해결하는지에 대해 공부해보았다. 이번 글에서는 세마포어로 해결할 수 있는 문제 중 생산자-소비자 문제에 대해 다뤄보려고 한다.
생산자-소비자 문제란?
생산자-소비자 문제는 두 프로세스가 협력하여 데이터를 주고받는 상황을 다룬다. 생산자-소비자 문제에서는 세 가지 용어를 먼저 정의해야 한다.
생산자(Producer) : 데이터를 생성하여 버퍼에 넣는 프로세스
소비자(Consumer) : 버퍼에서 데이터를 꺼내 사용하는 프로세스
버퍼(Buffer) : 생산자와 소비자 사이에 데이터를 저장하는 공유 공간
생산자-소비자 문제에는 두 가지 조건이 있다.
- 상호배제 : 여러 프로세스가 동시에 버퍼에 접근할 수 없다. 즉, 생산자가 버퍼에 데이터를 넣는 동안에는 소비자가 데이터를 꺼낼 수 없고, 반대로 소비자가 버퍼에서 데이터를 꺼내는 동안 생산자가 데이터를 넣을 수 없다.
- 유한 버퍼 : 버퍼의 크기는 유한하다. (n개의 슬롯) 따라서 버퍼가 가득 차면 생산자가 기다려야 하고, 버퍼가 비어 있으면 소비자가 기다려야 한다. 이 때문에 생산자-소비자 문제는 '유한 버퍼 문제'라고도 불린다.
따라서 버퍼에 대한 접근을 한 번에 한 프로세스로 제한하는 상호배제 문제가 해결되어야 하고, 버퍼 상태(full/empty)에 따라 실행의 순서를 조정하는 동기화 문제가 해결되어야 한다.
세마포어로 해결하기
세마포어 설정
세 가지의 세마포어를 사용한다.
- mutex : 상호배제를 보장. 초기값 = 1 (버퍼가 처음엔 사용 가능).
- empty : 버퍼의 빈 공간 수. 초기값 = n (버퍼가 처음엔 비어 있음).
- full : 버퍼에 있는 데이터 수. 초기값 = 0 (버퍼에 데이터 없음).
코드 구조
- 생산자 코드
while (true) {
데이터를 생산;
P(empty); // 빈 공간 확인
P(mutex); // 버퍼 잠금
버퍼에 데이터 넣기;
V(mutex); // 버퍼 잠금 해제
V(full); // 데이터 추가 신호
}
- 소비자 코드
while (true) {
P(full); // 데이터 존재 확인
P(mutex); // 버퍼 잠금
버퍼에서 데이터 꺼내기;
V(mutex); // 버퍼 잠금 해제
V(empty); // 빈 공간 추가 신호
데이터 소비;
}
해결 과정 (세마포어 작동 원리)
1. 상호배제 (mutex)
목적: 생산자와 소비자가 버퍼에 동시에 접근하지 못하게 함.
방법:
- 생산자와 소비자가 버퍼를 조작하기 전에 P(mutex)로 버퍼를 잠가서 독점함.
- 조작 후 V(mutex)로 잠금을 해제해 다음 프로세스가 접근 가능하도록 함.
- 초기값 = 1 : 버퍼가 처음에는 사용 가능하므로 첫 프로세스가 바로 접근 가능
2. 버퍼가 가득 찬 경우의 동기화 (empty)
목적: 버퍼가 가득 찼을 때 생산자를 대기시킴
세마포어 empty :
- 빈 공간의 수를 나타냄. 초기값 = n (최대 n개)
- empty = 0 이면, 버퍼가 가득 찬 상태.
동작:
- 생산자가 P(empty)를 호출 : 빈 공간이 있으면 (empty > 0), empty를 1 감소시키고 진행, 없으면 대기.
- 소비자가 데이터를 꺼낸 후 V(empty)를 호출 : 빈 공간이 생겼음을 알림. (empty 증가)
3. 버퍼가 빈 경우의 동기화 (full)
목적: 버퍼가 비었을 때 소비자를 대기시킴
세마포어 full :
- 버퍼에 있는 데이터 수를 나타냄. 초기값 = 0 (버퍼가 비어 있음)
- full = 0 이면, 꺼낼 데이터가 없는 상태
동작:
- 소비자가 P(full)를 호출 : 데이터가 있으면 (full > 0), full을 1 감소시키고 진행, 없으면 대기
- 생산자가 데이터를 넣은 후 V(full)을 호출 : 데이터가 추가되었음을 알림. (full 증가)
혼동하기 쉬운 empty와 full 용어 정리
여기서 empty와 full의 용어가 혼동되기 쉽다. 원래 단어 자체의 뜻이 empty는 비어 있다는 뜻이고 full은 가득 찼다는 뜻인데, 각각을 초기값 0과 함께 보면 그 의미가 완전히 반대이기 때문이다.
다시 정리해보면,
empty : 버퍼에 남아 있는 빈 슬롯의 개수
생산자가 버퍼에 데이터를 넣으려면 빈 슬롯이 필요함
empty = 0 은 빈 슬롯이 없음을 뜻하므로 생산자가 데이터를 넣지 못하도록 막음
따라서 empty는 버퍼가 가득 찼을 때 생산자를 대기시키는 동기화를 담당함
full : 버퍼에 있는 데이터의 수
소비자가 데이터를 꺼내려면 버퍼에 데이터가 있어야 함
full = 0 은 데이터가 없음을 뜻하므로 소비자가 꺼낼 데이터가 없어 대기하도록 함
따라서 full은 비어 있는 상태에서 소비자를 대기시키는 동기화를 담당함
실행 흐름 예시
'KNOU CS > 운영체제' 카테고리의 다른 글
가상 메모리와 주소 변환에 대해... 내가 이해하려고 쓰는 글✏ (0) | 2025.04.21 |
---|---|
[운영체제] 병행 프로세스 - 세마포어 완벽 정리 Part 3 - 판독기-기록기 문제 (0) | 2025.03.19 |
[운영체제] 병행 프로세스 - 세마포어 완벽 정리 Part 1 - 상호배제, 동기화 (feat. 비행기 화장실) (1) | 2025.03.12 |