Operating Systems : Three Easy Pieces를 보고 번역 및 정리한 내용들입니다.
지금은 간단하게 다음의 코드가 임계 영역이라 하자.
balance = balance + 1;
락을 사용하기 위해서는 다음과 같이 임계 영역의 주변을 몇 가지의 코드로 둘러싸면 된다.
lock_t mutex;
...
lock(&mutex);
balance = balance + 1;
unlock(&mutex);
락은 특정 시점의 락의 상태를 나타내는 변수다.이 상태에는
가 있다. 이외에도 어떤 스레드가 락을 가지고 있는지, 락 획득 순서를 위한 큐 등의 다른 정보들도 저장할 수 있지만, 이러한 정보들은 락의 사용자에게는 감추어져 있다.
lock()과 unlock() 루틴이 의미하는 바는 간단하다.
lock()lock()을 호출하면, 이는 락이 다른 스레드에 의해 소유되고 있는 동안에는 리턴하지 않는다. 한 스레드가 락을 쥐고 있는 동안 다른 스레드들이 임계 영역에 진입하는 일을 막기 위함이다.unlock()lock()을 호출해 대기 중인 상태가 아니라면), 락의 상태는 그냥 사용 가능한 상태로 유지된다.스레드는 보통 프로그래머에 의해 생성되지만, 제어까지 프로그래머가 하기보다는 OS 스케줄링에 맡기는 경우가 많다. 락은 그러한 제어 중 일부, 그러니까 스케줄링에 대한 최소한의 제어를 프로그래머들에게 다시 돌려준다. 락을 이용하면 하나보다 많은 스레드들이 동시에 임계 영역의 코드를 실행하지 못하도록 보장할 수 있으며, 따라서 전통적 OS 스케줄링이 가져다 주던 혼란을 좀 더 제어된 활동으로 바꿔준다.
POSIX 라이브러리에서는 락을 뮤텍스(mutex)라 부른다. 다음과 같은 POSIX 스레드 코드를 보면, 정확히 위에서와 같은 동작을 한다는 것을 알 수 있다.
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
Pthread_mutex_lock(&lock); // wrapper; exits on failure
balance = balance + 1;
Pthread_mutex_unlock(&lock);
위 코드를 보면 lock()과 unlock()에 락을 변수로 전달하는 것을 볼 수 있는데, 이렇게 하면 다양한 다양한 임계 영역에 따라 다양한 락을 사용할 수 있게 된다. 어떤 임계 영역에 들어가든 상관없이 하나의 큰 락만을 사용하는 것보다, 다른 데이터나 자료 구조에 대해서는 다른 락을 사용하도록 해줘야 더 많은 스레드들이 병행적으로 잠긴 코드에 접근할 수 있게 되기 때문이다.
어떻게 효율적인 락을 만들 수 있을까? 효율적인 락은 적은 비용으로 아래의 속성들을 제공한다. 어떤 하드웨어 지원이 필요할까? OS는 어떤 도움을 줄까?
잘 동작하는 락을 만들기 위해서는 하드웨어와 OS의 도움이 필요하다. 이를 위해 사용할 수 있는 여러 많은 서로 다른 하드웨어 명령어들이 있는데, 이 명령어들의 구현에 대해서는 다루지 않겠지만, 어떻게 이것들을 사용해야 락을 구현할 수 있을지에 대해서는 배우게 될 것이다. 또한 OS가 정교한 락 라이브러리를 완성시키는 데 어떤 도움을 주는지에 대해서도 배우게 될 것이다.
락을 만들어 보기전에, 우선은 목표가 무엇이고, 락 구현의 효율성을 어떻게 평가할 수 있는지에 대해 얘기해보자. 락이 (잘) 작동하는지 아닌지를 평가하려면 기본적인 기준을 세워야 한다.