세마포어로 푸는 생산자·소비자 문제
넣을 자리가 없을 때 생산자는 무엇을 해야 할까?
공유 버퍼
꽉 차도, 텅 비어도 멈춰야 합니다
생산자 태스크는 값을 만들어 버퍼에 넣고(DEPOSIT), 소비자 태스크는 버퍼에서 꺼내 씁니다(FETCH). 버퍼가 꽉 차면 생산자가, 비면 소비자가 기다려야 합니다. 3편에서 본 협력 동기화입니다.
버퍼는 값을 잠시 담아 두는 곳입니다. 세 칸은 예시입니다.
세마포어 둘
빈 자리와 찬 자리를 따로 셉니다
세마포어를 둘 둡니다. emptyspots의 카운터는 빈 자리 수이고 버퍼 길이에서 시작합니다. fullspots의 카운터는 찬 자리 수이고 0에서 시작합니다.
세마포어는 4편에서 본, 카운터와 대기 큐로 된 자료 구조입니다.
생산자
빈 자리를 얻어야 넣습니다
생산자는 emptyspots에 wait를 불러 빈 자리를 얻고, 값을 넣은 뒤 fullspots에 release를 불러 찬 자리가 늘었음을 알립니다. 빈 자리가 없으면 큐에서 기다립니다.
이 강의가 따르는 교재의 의사코드입니다. 생산자는 이 세 줄을 되풀이합니다.
소비자
꺼내고 나서 생산자를 깨웁니다
소비자는 거꾸로입니다. fullspots에 wait를 부르고, 값을 꺼낸 뒤 emptyspots에 release를 부릅니다. 이 release가 큐에서 기다리던 생산자를 깨웁니다.
깨울 때는 카운터가 0 그대로입니다. 생긴 빈 자리를 깨어난 생산자가 곧바로 채우기 때문입니다.
경쟁 동기화까지
버퍼를 건드릴 때만 잠급니다
두 세마포어는 발만 맞출 뿐, 둘이 한꺼번에 버퍼를 건드리는 것은 막지 못합니다. 그래서 4편의 이진 세마포어 access를 더해, 버퍼에 손대는 줄만 감쌉니다.
소비자도 같습니다. wait(fullspots), wait(access), FETCH(VALUE), release(access), release(emptyspots) 순서입니다.
세마포어의 약점
빠뜨려도 모릅니다
세마포어는 쓰는 사람이 wait와 release를 빠짐없이 제자리에 적어야 합니다. 한 줄을 빠뜨려도 컴파일러(실행 전에 번역하며 오류를 찾는 프로그램)는 잡아내지 못합니다.
교착 상태는 서로 기다리다 모두 멈추는 상태입니다(3편).
다음 편