도서관 열람실의 게시판을 생각하자. 읽기만 하는 사람은 여럿이 함께 봐도 괜찮다. 하지만 누가 게시물을 고치는 동안에는 아무도 읽거나 고치면 안 된다. 독자끼리는 함께, 저자는 혼자 들어가게 하면 단순한 상호 배제보다 훨씬 많은 사람이 동시에 일할 수 있다. 대신 독자를 너무 우대하면 독자가 끊이지 않는 동안 저자가 영원히 못 들어가고(기아), 저자를 우대하면 반대가 된다.
예시로 보기
게시판에 독자 R1, R2와 저자 W가 접근한다. 독자 우선 방식에서 다음 순서가 생길 수 있다.
| 시점 | 일 | 안에 있는 사람 |
|---|---|---|
| 1 | R1 들어옴 (첫 독자 → 저자를 막음) | R1 |
| 2 | W 도착 → 막힘 | R1 |
| 3 | R2 들어옴 (이미 독자가 있으니 바로) | R1, R2 |
| 4 | R1 나감 | R2 |
| 5 | R3 들어옴 | R2, R3 |
| 6 | R2 나감, … | R3 |
독자가 겹치며 계속 들어오면 마지막 독자가 나가는 순간이 오지 않는다. W는 계속 기다린다.
정확히 말하면
여러 프로세스가 한 데이터 영역을 함께 쓴다. 일부는 읽기만 하고(독자), 일부는 쓰기만 한다(저자). 지켜야 할 조건은 셋이다.
- 여러 독자가 동시에 읽을 수 있다.
- 한 번에 한 저자만 쓸 수 있다.
- 저자가 쓰는 동안에는 어떤 독자도 읽을 수 없다.
독자 우선
readcount는 지금 읽고 있는 독자 수, x는 readcount를 보호하는 세마포어, wsem은 쓰기 권한이다.
int readcount = 0;
semaphore x = 1, wsem = 1;
void reader() { void writer() {
while (true) { while (true) {
semWait(x); semWait(wsem);
readcount++; WRITEUNIT();
if (readcount == 1) semWait(wsem); semSignal(wsem);
semSignal(x); }
READUNIT(); }
semWait(x);
readcount--;
if (readcount == 0) semSignal(wsem);
semSignal(x);
}
}
첫 독자만 wsem을 잡고, 마지막 독자만 놓는다. 그 사이의 독자들은 wsem을 거치지 않고 바로 들어간다. 그래서 독자가 하나라도 남아 있는 한 저자는 들어갈 수 없다. 저자가 굶을 수 있다.
저자 우선
저자가 쓰겠다고 알리면, 그 뒤로는 새 독자가 들어오지 못하게 한다. 독자 쪽에 rsem(저자가 하나라도 기다리면 독자를 막는 세마포어)을 더하고, 저자 쪽에 writecount와 이를 보호하는 y를 더한다. 첫 저자가 rsem을 잡고 마지막 저자가 놓는다. 독자 우선과 대칭이다. 이번에는 저자가 끊이지 않으면 독자가 굶을 수 있다.
메시지 전달로도 저자 우선을 구현할 수 있다. 공유 데이터에 접근하는 제어 프로세스를 하나 두고, 독자와 저자가 그 프로세스에 요청 메시지를 보낸다.
활용
- 데이터베이스, 캐시, 설정값처럼 읽기가 쓰기보다 훨씬 잦은 데이터에 쓴다.
- 자바의
ReentrantReadWriteLock, POSIX의pthread_rwlock_t가 읽기-쓰기 잠금이다. 공평성 옵션으로 기아를 줄인다.
연결
- 선수: 세마포어
- 같은 계열의 고전 문제: 생산자-소비자 문제
- 생산자-소비자와의 차이: 생산자-소비자에서는 소비자도 버퍼를 바꾼다(물건을 꺼낸다). 그래서 소비자끼리도 상호 배제가 필요하다. 독자-저자의 독자는 데이터를 바꾸지 않으므로 함께 들어갈 수 있다.
확인 문제
C1 독자-저자 문제가 지켜야 할 세 조건을 쓰라.
답: ① 여러 독자가 동시에 읽을 수 있다 ② 한 번에 한 저자만 쓴다 ③ 저자가 쓰는 동안 어떤 독자도 읽지 못한다.
C2 독자 우선 해법에서 저자가 굶을 수 있는 이유를 코드의 readcount로 설명하라.
답: wsem은 readcount가 1이 될 때 잡히고 0이 될 때만 풀린다. 앞 독자가 나가기 전에 새 독자가 계속 들어오면 readcount가 0이 되지 않는다. 그동안 wsem이 풀리지 않아 저자는 영원히 기다린다.
C3 독자 코드에서 semWait(x)와 semSignal(x)를 빼면 어떤 순서에서 저자와 독자가 함께 들어가는가?
답: R1이 읽는 중이라 readcount = 1이고 wsem은 잡혀 있다.
- R1이 다 읽고
readcount--를 시작해 1을 읽는다. - R2가 도착해
readcount++로 1을 읽고 2를 쓴다. 2는 1이 아니므로wsem을 잡지 않고 바로 읽기 시작한다. - R1이 아까 읽은 1에서 1을 빼 0을 쓴다. 0이므로
semSignal(wsem). - 기다리던 저자 W가
wsem을 얻어 쓰기 시작한다. R2는 아직 읽는 중이다.
readcount를 고치는 일 자체가 경쟁 조건이 되어 조건 3이 깨진다.