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가 정교한 락 라이브러리를 완성시키는 데 어떤 도움을 주는지에 대해서도 배우게 될 것이다.
락을 만들어 보기전에, 우선은 목표가 무엇이고, 락 구현의 효율성을 어떻게 평가할 수 있는지에 대해 얘기해보자. 락이 (잘) 작동하는지 아닌지를 평가하려면 기본적인 기준을 세워야 한다.