세마포어는 남은 자원의 개수를 세는 카운터입니다. 스레드는 자원을 쓰기 전에 wait()로 카운터를 1 줄이고, 다 쓰면 signal()로 1 늘립니다. 카운터가 0이면 wait()를 호출한 스레드는 자원이 돌아올 때까지 기다립니다.
자리가 세 개인 주차장을 세마포어로 관리하는 시뮬레이터
주차 자리 3개를 세마포어 S로 관리합니다
[차 한 대 도착]을 여러 번 눌러 자리를 채워 보세요. 주차된 차를 누르면 그 차가 나가면서 signal()을 호출합니다.
wait()와 signal()
wait()를 호출한 스레드는 S가 0보다 크면 S를 1 줄이고 바로 진행합니다. S가 0이면 그 자리에서 멈춰 기다립니다. signal()을 호출한 스레드는 S를 1 늘리고, 기다리는 스레드가 있으면 하나를 깨웁니다. 깨어난 스레드가 그 자원을 바로 가져가므로, 기다리는 차가 있을 때는 S가 0에 머뭅니다.
칸이 다섯 개인 버퍼를 두고 생산자와 소비자가 세마포어로 조율하는 시뮬레이터
생산자-소비자: 칸이 5개인 버퍼를 empty, full 두 세마포어로 관리합니다
버튼으로 한 번씩 넣고 꺼내거나, [자동 실행 시작]을 눌러 보세요. 생산이 소비보다 빠르면 버퍼가 가득 차서 생산자가 멈추고, 반대면 소비자가 멈춥니다.
생산자-소비자 문제
생산자는 버퍼에 빈 칸이 있어야 넣을 수 있고, 소비자는 찬 칸이 있어야 꺼낼 수 있습니다. 빈 칸 수를 세는 empty와 찬 칸 수를 세는 full, 두 세마포어로 이 조건을 지킵니다. 버퍼 자체를 동시에 건드리지 않도록 뮤텍스도 함께 씁니다.
생산자 소비자
wait(empty) wait(full)
lock(m) lock(m)
버퍼에 넣기 버퍼에서 꺼내기
unlock(m) unlock(m)
signal(full) signal(empty)
뮤텍스와 무엇이 다른가
뮤텍스는 잠근 스레드만 풀 수 있지만, 세마포어에는 주인이 없어서 어느 스레드든 signal()을 호출할 수 있습니다. 그래서 세마포어는 자원 개수를 세는 일 외에 "일이 생겼다"는 신호를 보내는 데도 씁니다. FreeRTOS에서 인터럽트 핸들러가 xSemaphoreGiveFromISR()로 태스크를 깨우는 것이 대표적인 예입니다.