Operating Systems : Three Easy Pieces를 보고 번역 및 정리한 내용들입니다.
물론 다른 임계 영역도 가능하겠지만, 지금은 간단하게 다음과 같은 임계 영역을 생각하자.
balance = balance + 1;
락을 사용하기 위해서는 다음과 같이 임계 영역의 주변을 몇 가지의 코드로 둘러싸면 된다.
lock_t mutex;
...
lock(&mutex);
balance = balance + 1;
unlock(&mutex);
락은 일종의 변수일 뿐이다. 이를 사용하기 위해서는 어떤 락 변수를 선언해야 한다. 이 락 변수는 특정 시점의 락의 상태를 가지고 있는데, 이 상태에는 어떤 스레드도 락을 소유하고 있지 않아 사용 가능한 상태(available, unlocked, free)와 특정한 한 스레드가 락을 가지고 있는 상태(acquired, locked, held)가 있다. 이외에도 어떤 스레드가 락을 가지고 있는지, 락 획득 순서를 위한 큐 등의 다른 정보들도 저장할 수 있지만, 이러한 정보들은 락의 사용자에게는 감추어져 있다.
lock()
과 unlock()
루틴이 의미하는 바는 간단하다. 루틴 lcok()
을 호출하는 것은 락을 얻으려 시도하는 것이다. 만약 어떤 스레드도 락을 가지고 있지 않은 상태라면 스레드는 락을 얻고 임계 영역으로 진입한다. 이 스레드는 종종 락의 소유자(owner)로 불린다. 만약 이때 다른 스레드가 해당 락 변수에 대해 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);
위 코드를 보면 락과 언락에 변수를 전달한다는 것을 볼 수 있다. 이렇게 변수를 전달함으로써, 다른 (공유) 변수들을 보호하기 위해 다른 락을 사용할 수 있게 된다. 이렇게 하는 이유는 병행성을 높이기 위함이다. 어떤 임계 영역에 들어가든지 상관없이 하나의 큰 락만을 사용하는 것보다, 다른 데이터나 자료 구조에 대해서는 다른 락을 사용하는 것이 더 많은 스레드들이 각자 잠긴 코드에 접근할 수 있게 하기 때문이다.
어떻게 효율적인 락을 만들 수 있을까? 효율적인 락은 상호 배제를 적은 비용으로 제공하며, 아래에서 논의할 몇몇 다른 속성들도 제공한다. 어떤 하드웨어 지원이 필요할까? OS는 어떤 도움을 줄까?
작동하는 락을 만들기 위해서는 하드웨어와 OS의 도움이 필요하다. 오랫동안 많은 수의 서로 다른 하드웨어 명령어들이 다양한 컴퓨터 구조의 명령어 집합에 추가되어 왔다. 이 명령어들이 어떻게 구현되었는지에 대해서는 다루지 않겠지만, 락과 같은 상호 배제를 만들기 위해서 이들을 어떻게 사용하는지에 대해서는 배우게 될 것이다. 또한 OS가 정교한 락 라이브러리를 완성시키는 데에 어떤 도움을 주는지에 대해서도 다루게 될 것이다.
락을 만들어 보기전에, 우선은 목표가 무엇이고, 특정 락 구현의 효율성을 어떻게 평가할 수 있는지에 대해 얘기해보자. 락이 (잘) 작동하는지 아닌지를 평가하려면 기본적인 기준을 세워야 한다.