세마포어는 자원의 개수와 작업 완료를 어떻게 표현할까
세마포어는 정수 카운터와 원자적인 획득·반환 동작으로 동시 실행을 조절하는 도구다. 허용량이 남으면 작업이 하나를 사용하고 진행하며, 없으면 기다린다. 허용량을 돌려주면 다른 작업이 진행할 수 있다. 하나씩만 들어가게 하는 상호 배제부터 여러 자원의 동시 사용, 작업 사이의 완료 알림까지 같은 기본 장치를 다른 방식으로 사용할 수 있다.
세마포어 카운터가 세는 대상과 획득·반환 규칙을 함께 정해야 한다.
카운터에 붙인 의미가 중요하다
데이터베이스 연결 세 개를 가진 풀을 생각해 보자. 세마포어의 초기 허용량을 3으로 둔다. 요청은 연결을 사용하기 전에 wait로 허용량을 얻고, 사용이 끝나면 post로 돌려준다. 세 요청이 모두 사용하는 동안 네 번째 요청은 기다린다. 단, 세마포어는 연결 객체 자체를 고르는 자료구조까지 제공하지 않는다. 풀 안의 객체 목록도 올바르게 관리해야 한다.
카운터 감소와 가능 여부 확인은 하나의 원자적인 동작이어야 한다. 두 작업이 허용량 1을 동시에 확인하고 각각 사용하면 제한을 넘기기 때문이다. POSIX의 sem_wait 설명은 값이 양수일 때 감소시키며, 즉시 감소할 수 없으면 대기하는 동작을 규정한다. 교육용 표현에서 음수로 대기자 수를 나타내기도 하지만 모든 API가 이를 외부에 그대로 보여 주는 것은 아니다.
허용량을 반환하는 경로까지 설계한다
획득한 작업이 오류나 예외로 끝나더라도 허용량을 돌려주지 않으면 다른 작업이 계속 기다릴 수 있다. 반대로 획득하지 않았는데 반환하거나 한 번 획득하고 두 번 반환하면 동시 사용 한도가 무너진다. 자원 수 제한은 카운터 초기화만으로 끝나지 않는다. 획득 성공 여부, 객체 수명, 모든 종료 경로의 반환을 연결해야 한다.
초기값을 0으로 두면 다른 의미를 만들 수 있다. A가 작업을 끝낸 뒤 post하고, B가 wait한 뒤 이어서 진행하면 완료 알림이 된다. A가 먼저 끝나도 허용량이 남아 B가 나중에 받을 수 있다. 알림 자체를 기억하지 않는 조건 변수와 비교할 때 세마포어의 카운터가 가진 의미가 드러난다.
뮤텍스와 닮았지만 같지는 않다
초기값 1인 이진 세마포어는 한 작업만 통과하게 하는 데 쓸 수 있다. 그러나 일반적인 뮤텍스는 소유권이 있어 획득한 실행 흐름이 해제한다. 세마포어는 생산자가 허용량을 추가하고 소비자가 가져가는 것처럼 다른 실행 흐름이 두 동작을 맡을 수 있다. 따라서 둘을 무조건 교환 가능한 잠금으로 생각하면 규약을 놓친다.
대기 순서의 공정성은 구현과 API에 따라 다르다. 먼저 기다렸다고 항상 먼저 깨어난다고 가정하면 안 된다. 세마포어를 여러 개 반대 순서로 획득하면 교착 상태도 생길 수 있다. 허용량이 충분해도 공유 자료의 복합 갱신까지 자동으로 보호되는 것은 아니므로 별도의 임계 구역이 필요한지 판단해야 한다.
세마포어는 동시 요청 수, 작업 파이프라인의 슬롯, 생산된 항목 수를 표현할 때 유용하다. 올바른 사용의 출발점은 이 카운터가 무엇을 세는지 한 문장으로 말하는 것이다. 사용 가능한 연결인지, 비어 있는 버퍼 칸인지, 완료된 작업인지가 명확하면 초기값과 증감 위치, 실패 시 복구 규칙도 자연스럽게 정해진다.