Mutex
뮤텍스(mutex)는 락(lock)을 가진 프로세스만이 공유 자원에 접근할 수 있게 하는 방법이다. 개념을 이해하기 쉽도록 유명한 화장실 예(toilet example)를 들어 보자.
뮤텍스의 작동 방식은 화장실과 화장실 열쇠가 하나뿐인 식당과 같다.

식당에는 화장실 한 칸과, 화장실 문을 열 수 있는 열쇠 한 개가 있다.
A가 열쇠를 가지고 화장실에 간다.
화장실에 가려던 B는 열쇠가 없어서 기다린다.
A가 화장실에서 나와 열쇠를 반납하면, 기다리던 B가 열쇠를 가지고 화장실에 간다.
여기서 화장실은 공유 자원을 포함한 임계 영역을, 열쇠는 락을, A와 B는 공유 자원에 접근하려는 프로세스를 의미한다. 임계 영역에 먼저 접근한 프로세스가 임계 영역에 락을 걸면, 다른 프로세스들은 해당 프로세스가 락을 해제하기 전까지 대기해야 한다.
이처럼 임계 영역에 접근한 프로세스가 임계 영역에 락을 건다고 해서, 락킹 매커니즘(locking mechanism)이라고도 한다.
임계 영역에 접근하지 못한 프로세스는 락을 얻기 위해 기다리는 동안, 락이 풀렸는지 반복문을 돌면서 확인한다. 이를 바쁜 대기(busy waiting)의 한 종류인 스핀락이라고 한다.
바쁜 대기(busy waiting): 프로세스가 공유 자원에 접근할 수 있는 권한을 얻을 때까지 확인하는 과정을 일컫는다.
즉, 스핀락(spinlock)은 락을 얻기 위해 프로세스가 반복문을 돌면서 기다리는 것을 의미한다. 프로세스가 대기 상태가 되지 않고 반복문을 돌면서 자원의 사용 가능 여부를 확인하므로 프로세스가 빠르게 교체될 수 있다.
Code Example (TypeScript)
/**
* 화장실 사용 예제
*/
async function toiletExample() {
let inToilet: string | null = null; // 현재 화장실을 사용 중인 사람
const waitingQueue: string[] = []; // 대기 중인 사람들의 대기열
// 화장실 뮤텍스 (화장실 열쇠)
const toiletLock = {
acquire: async function(personName: string) {
if (inToilet === null) {
// 화장실이 비어 있으면 즉시 입장
inToilet = personName;
return;
}
// 화장실이 사용 중이면 대기열에 추가
waitingQueue.push(personName);
console.log(`대기 중인 사람: ${waitingQueue.length}명`);
// 대기 - 이 프로미스는 화장실에 들어갈 수 있을 때 해결됨
return new Promise<void>((resolve) => {
const checkInterval = setInterval(() => {
if (inToilet === personName) {
clearInterval(checkInterval);
resolve();
}
}, 100);
});
},
release: function(personName: string) {
console.log(`${personName}님이 화장실에서 나왔습니다. (열쇠를 반납했습니다)`);
if (waitingQueue.length > 0) {
// 대기 중인 사람이 있으면 첫 번째 사람을 입장시킴
const nextPerson = waitingQueue.shift()!;
inToilet = nextPerson;
console.log(`${nextPerson}님이 화장실에 들어갔습니다. (열쇠를 획득했습니다)`);
console.log(`대기 중인 사람: ${waitingQueue.length}명`);
} else {
// 대기 중인 사람이 없으면 화장실을 비움
inToilet = null;
}
}
};
// 화장실 사용 함수
async function useToilet(personName: string, usageTime: number, delay: number): Promise<void> {
// 요청 전 대기
await new Promise(resolve => setTimeout(resolve, delay));
console.log(`${personName}님이 화장실을 사용하려고 합니다.`);
// 화장실 입장 시도
await toiletLock.acquire(personName);
// 첫 번째 사람 또는 대기 없이 들어간 사람만 여기서 로그 출력
if (waitingQueue.length === 0 && personName === 'A') {
console.log(`${personName}님이 화장실에 들어갔습니다. (열쇠를 획득했습니다)`);
}
// 화장실 사용 중 (임계 영역 작업)
await new Promise(resolve => setTimeout(resolve, usageTime));
// 화장실 퇴장 (로그는 release 함수 내에서 출력)
toiletLock.release(personName);
}
console.log("초기 화장실 상태: 총 1칸 사용 가능");
// A, B, C가 각각 다른 시간에 화장실을 사용하려고 함
const promiseA = useToilet('A', 2000, 0); // A는 바로 시작
const promiseB = useToilet('B', 1000, 500); // B는 0.5초 후에 시작
const promiseC = useToilet('C', 3000, 1000); // C는 1초 후에 시작
// 모든 사람이 화장실 사용을 마칠 때까지 기다림
await Promise.all([promiseA, promiseB, promiseC]);
console.log("모든 사람이 화장실 사용을 마쳤습니다.");
}
// 예제 실행
toiletExample();
위 코드는 앞서 설명한 뮤텍스의 개념을 실제로 구현한 것이며 특징은 다음과 같습니다.
락 메커니즘:
lock()
함수는 화장실 열쇠(공유 자원에 대한 접근 권한)를 획득하는 과정입니다. 이미 다른 프로세스가 락을 보유하고 있으면, 현재 프로세스는 대기열에 추가되어 락이 해제될 때까지 대기합니다.대기열 관리: 대기 중인 프로세스들은
waitQueue
배열에 저장됩니다. 이는 화장실을 사용하기 위해 줄을 서서 기다리는 상황을 모델링합니다.락 해제와 다음 프로세스 선택:
unlock()
함수는 화장실 열쇠를 반납하는 과정입니다. 대기 중인 프로세스가 있으면, 대기열에서 첫 번째 프로세스가 락을 획득하게 됩니다.비동기 처리: 자바스크립트/타입스크립트에서는 실제 스레드 기반 병렬 실행이 아닌 비동기 실행 모델을 사용하므로, Promise를 활용하여 락 획득 대기를 구현했습니다. 이는 앞서 설명한 바쁜 대기(busy waiting)와는 다른 방식이지만, 개념적으로 동일한 동기화 메커니즘을 제공합니다.
이 구현은 실제 운영체제의 뮤텍스보다 단순화된 형태이지만, 기본 개념(한 번에 하나의 프로세스만 임계 영역에 접근할 수 있도록 제어하는 것)을 명확하게 보여줍니다. 특히 화장실 예제를 통해 설명한 뮤텍스의 동작 방식을 코드로 직접 확인할 수 있습니다.

실제 실행 시, 위 이미지와 같이 A, B, C가 동시에 화장실을 사용하려고 시도하지만, 뮤텍스로 인해 한 번에 한 사람만 화장실에 들어갈 수 있습니다. 이것이 바로 공유 자원에 대한 접근을 제어하는 뮤텍스의 핵심 기능입니다.