read-writers lock — 공유 자원 접근하기
Multithreaded programming에서 공유 자원에 접근할 때는 동시에 두 개 이상의 스레드가 자원을 변경시키지 않기 위해서 mutex를 사용한다. Mutex를 사용하면 공유자원에 접근하는 스레드를 한 개로 제한하기 때문에 안전하지만, 어떤 경우는 비효율적이다. 예를 들어 여러 스레드가 공유 자원에 동시에 접근해야 하지만 그중 일부 스레드만 값을 변경하는 경우는 어떨까? 이런 경우 값을 읽기만 하는 스레드는 동시에 접근해도 상관없다. 하지만 어떤 스레드가 값을 변경하고 있으면, 다른 스레드는 공유 자원에 접근해서는 안 된다. 반대로 다른 스레드가 공유 자원에 접근하고 있는 중에는 값을 변경하는 스레드는 접근해서는 안 된다.
이때 사용하는 것이 shared-exclusive lock이라고도 하는 readers-writer lock이다. Readers-writer lock은 여러 개의 reader와 한 개의 writer를 허용한다. 그래서 multiple-readers/single-writer lock(MRSW lock)이라고도 불린다. 즉, 이미 read lock이 잡혀있는 readers-writer lock에 read lock을 잡으면 바로 lock이 잡히고 다음 코드를 실행할 수 있지만, write lock을 잡으면, lock을 잡지 못하고 read lock이 풀릴 때까지 기다린다.
그렇다면 A thread가 read lock을 잡고, B thread가 write lock을 잡은 뒤 C thread가 read lock을 잡으면 어떻게 될까? 단순하게 생각해보면 A thread가 read lock을 잡고 있으니 B thread의 write lock은 잡히지 못하고 기다리고, C thread의 read lock은 잡힐 수 있기 때문에, A thread와 C thread의 코드가 실행될 것이다. 하지만 이런 구현의 경우 read lock이 빈번하게 잡히는 코드라면, write lock이 영원히 실행되지 못할 수도 있다. 이 현상을 write-starvation이라고 부른다.
그래서 readers-write lock의 구현 중에서는 이미 read lock이 잡혀서 read lock을 잡을 수 있는 상황에서도 write lock이 기다리고 있으면 새 read lock은 잡지 않고, write lock이 잡히는 것을 기다리는 구현도 있다. 이 경우 불필요하게 read lock을 못 잡고 기다리는 경우도 있기 때문에 효율성은 떨어진다. 하지만 먼저 기다리기 시작한 lock이 먼저 잡히기 때문에 공정성은 올라간다. Write-starvation이 발생할 수 있지만, read lock을 효율적으로 잡을 수 있는 구현을 read-preferring이라고 부른다. 반대로 공정하지만, 효율성이 약간 떨어지는 구현을 write-preferring이라고 부른다. 이 둘 중 어느 쪽이 좋은지는 상황에 따라 다르다. read lock을 빈번하게 잡았다 풀어 write-lock starvation이 발생할 수 있는 경우는 write-preferring lock이 좋다. 하지만 write-starvation이 발생할 걱정이 없는 경우는 read-preferring의 구현이 더 가볍고, 이미 read lock이 잡혀있는 MRSW lock에 대해서 같은 스레드에서 다시 한번 read lock을 잡을 수 있다.
어떤 구현은 이 둘 중 한 가지 정책을 사용한다고 명시하지만, 구현에 따라서는 어떤 정책을 사용하는지 명시하지 않는 경우도 있다. 어떤 정책인지 명시하지 않은 경우는 대부분 추후에 최적화할 여지를 남겨두기 위해서이기 때문에 이 경우 read-preferring이든 write-preferring이든 안전하도록 코드를 짜야 한다.
Originally published at blog.seulgi.kim on February 13, 2019.