스핀락과 뮤텍스의 차이점 정리

스핀락과 뮤텍스는 둘 다 임계 구역에 한 번에 하나의 스레드만 들여보내는 락이지만, 락이 풀리기를 기다리는 방법이 다릅니다. 스핀락은 루프로 락 변수를 계속 확인하는 바쁜 대기로 기다리므로 기다리는 동안에도 CPU를 계속 쓰지만, 풀리면 바로 잡을 수 있습니다. 뮤텍스는 기다리는 스레드를 슬립시켜 CPU를 내놓고, 락이 해제되면 OS가 깨워 줍니다. 보유 시간이 아주 짧은 경우나 잠들 수 없는 인터럽트 핸들러 안에서는 스핀락을, 오래 보유하는 경우나 보유 중에 잠들 가능성이 있는 경우에는 뮤텍스를 사용합니다.

비교 항목스핀락뮤텍스
대기 방식락이 풀릴 때까지 루프로 변수를 계속 확인(바쁜 대기)풀려 있지 않으면 슬립해서 기다리고, 해제될 때 깨워 줌
대기 중의 CPU계속 사용내놓음. 그동안 다른 스레드가 CPU를 쓸 수 있음
대기 비용컨텍스트 스위치가 없고, 풀리면 바로 잡을 수 있음잠들 때와 깨어날 때 시스템 콜과 컨텍스트 스위치 비용이 듦
적합한 보유 시간아주 짧은 시간(컨텍스트 스위치에 걸리는 시간보다 짧음)긴 시간. 보유 중에 I/O를 기다리는 등 잠들 가능성이 있을 때
보유 중에 잠들기해서는 안 됨. 기다리는 쪽이 CPU를 계속 쓰게 됨가능
코어가 하나일 때기다리는 동안 락을 가진 스레드가 실행되지 못해, 돌아도 풀리지 않음. 커널에서는 선점을 멈추고 사용문제없이 사용 가능
커널 안에서의 사용인터럽트 핸들러처럼 잠들 수 없는 상황(인터럽트 컨텍스트)에서도 사용 가능. 인터럽트와 데이터를 공유할 때는 인터럽트를 금지한 뒤 획득잠들 가능성이 있으므로 프로세스 컨텍스트에서만 쓸 수 있고, 인터럽트 핸들러에서는 쓸 수 없음
우선순위 역전락을 가진 스레드가 멈추면 기다리는 쪽이 계속 돌게 됨. 커널에서는 보유 중 선점을 금지해 방지우선순위 상속이 있는 뮤텍스로 대책 가능
구현 예원자적 명령(test-and-set이나 compare-and-swap)으로 변수를 바꿈. 평소에는 일반 읽기로 기다리는 test-and-test-and-set으로 코어 간 통신을 줄임Linux의 futex에서는 경합이 없으면 사용자 공간의 원자적 명령만으로 락을 걸고, 경합했을 때만 커널이 재워 줌. 잠시 돈 뒤에 잠드는 구현도 있음

수치는 대략적인 자릿수 기준이며, 공정 세대와 제품에 따라 달라져요.

상황별 선택 기준

스핀락
멀티코어에서 락을 가지는 시간이 아주 짧을 때(컨텍스트 스위치 비용보다 짧을 때). 인터럽트 핸들러 등 잠드는 것이 허용되지 않는 커널 안의 처리.
뮤텍스
락을 가지는 시간이 길 때, 보유 중에 I/O나 메모리 할당 등으로 잠들 가능성이 있을 때. 사용자 공간 애플리케이션의 일반적인 상호 배제.
조합
많은 뮤텍스 구현은 경합이 없으면 슬립하지 않고 원자적 명령만으로 획득하고, 획득하지 못하면 잠시 돈 뒤에 잠드는 등 두 방식의 장점을 조합합니다.

자주 묻는 질문

스핀락은 CPU를 낭비하는데 왜 쓰이나요?

락을 가지는 시간이 아주 짧으면, 잠들었다 깨어나기 위한 컨텍스트 스위치 비용보다 잠시 돌며 기다리는 편이 빠르기 때문입니다. 또한 커널의 인터럽트 핸들러처럼 잠드는 것이 허용되지 않는 상황에서는 스핀락만 쓸 수 있습니다.

인터럽트 핸들러에서 뮤텍스를 쓸 수 없는 이유는 무엇인가요?

뮤텍스는 락을 획득하지 못하면 기다리는 스레드를 재우지만, 인터럽트 핸들러는 특정 스레드로서 동작하지 않으므로 잠들었다가 나중에 깨워 받을 수 없기 때문입니다. 인터럽트 핸들러와 데이터를 공유할 때는 스핀락과 인터럽트 금지를 조합해 사용합니다.

futex란 무엇인가요?

Linux에서 뮤텍스 등을 빠르게 구현하기 위한 구조입니다. 경합이 없을 때는 사용자 공간에서 원자적 명령으로 락 변수를 바꾸는 것만으로 끝내고, 경합해서 기다려야 할 때만 시스템 콜로 커널이 재워 줍니다. 대부분의 락이 경합하지 않는 경우에 시스템 콜 비용을 줄일 수 있습니다.

코어가 하나인 시스템에서 스핀락을 쓰면 어떻게 되나요?

사용자 공간에서 돌며 기다려도 락을 가진 스레드는 그 코어에서 실행될 수 없으므로, 자신의 할당 시간을 다 쓸 때까지 락은 풀리지 않습니다. 그래서 코어가 하나인 시스템의 커널에서는 스핀락 대신 선점이나 인터럽트를 금지해 상호 배제를 합니다.

이해도 확인

스핀락에 대한 설명으로 올바른 것은 무엇입니까?

  1. A락이 풀릴 때까지 스레드를 재워 OS가 깨워 주기를 기다리는 방식의 락
  2. B락이 풀릴 때까지 루프로 락 변수를 계속 조사하며 기다리는 방식의 락
  3. C디스크를 회전시켜 데이터를 지키는 구조
정답과 해설 보기

정답: B. 락이 풀릴 때까지 루프로 락 변수를 계속 조사하며 기다리는 방식의 락

스핀락은 원자적 명령으로 락을 얻으려 하고, 얻지 못하면 곧바로 다시 시도하기를 반복합니다. OS를 호출하여 재우고 깨우는 부담이 없으므로, 락을 쥐는 시간이 매우 짧은 경우에 빠르게 동작합니다. OS 커널 내부나 인터럽트 핸들러와의 배타 제어 등에서 자주 사용됩니다. 한편 기다리는 동안에도 CPU를 계속 사용한다는 단점이 있습니다.

스핀락이 적합하지 않은 상황으로 가장 적절한 것은 무엇입니까?

  1. A락을 쥐는 시간이 길어 기다리는 스레드가 CPU를 낭비하게 되는 상황
  2. B락을 쥐는 시간이 수십 나노초로 매우 짧은 상황
  3. COS 커널 내부에서 재울 수 없는 처리의 상황
정답과 해설 보기

정답: A. 락을 쥐는 시간이 길어 기다리는 스레드가 CPU를 낭비하게 되는 상황

스핀락으로 기다리는 스레드는 락이 풀릴 때까지 CPU를 계속 사용합니다. 락을 쥐는 시간이 길면 그 CPU 시간이 모두 낭비되고 다른 스레드의 실행도 방해합니다. 특히 코어가 하나뿐이면 락을 쥔 스레드가 실행되지 않는 한 풀리지 않으므로, 대기가 시간 할당이 끝날 때까지 계속됩니다. 긴 대기에는 재워서 기다리는 뮤텍스를 사용하며, 짧은 시간만 돌다가 재우는 방식도 있습니다.

스핀락에서 락 변수를 곧바로 원자적 명령으로 갱신하려 하지 않고, 평소에는 일반 읽기로 풀리기를 기다리다가 풀린 것으로 보일 때만 원자적 명령을 사용하는(test-and-test-and-set) 이유로 올바른 것은 무엇입니까?

  1. A일반 읽기가 원자적 명령보다 정확하기 때문
  2. B원자적 명령은 하나의 프로그램에서 한 번밖에 사용할 수 없기 때문
  3. C읽기만 한다면 자신의 캐시 안에서 기다릴 수 있으므로, 캐시 라인이 코어 사이를 오가는 통신을 크게 줄일 수 있기 때문
정답과 해설 보기

정답: C. 읽기만 한다면 자신의 캐시 안에서 기다릴 수 있으므로, 캐시 라인이 코어 사이를 오가는 통신을 크게 줄일 수 있기 때문

원자적 명령은 쓰기를 수반하므로, 기다리는 스레드가 반복해서 실행하면 그때마다 라인을 M 상태로 서로 빼앗으며 다른 코어의 사본을 계속 무효화합니다. 일반 읽기라면 각 코어는 S 상태의 사본을 자신의 캐시에서 계속 읽을 뿐이므로 통신이 일어나지 않습니다. 락이 해제되어 값이 바뀌었을 때만 원자적 명령으로 획득하러 갑니다.

  1. 뮤텍스(재워서 기다리는 락)와 스핀락을 비교한 설명으로 올바른 것은 무엇입니까?
  2. RTOS에서 발생하는 우선순위 역전의 설명으로 올바른 것은 무엇입니까?
  3. 우선순위 역전을 막기 위해 사용되는 '우선순위 상속'의 개념으로 올바른 것은 무엇입니까?

멀티코어와 동기화 문제 풀기

주요 용어 설명

스핀락(spinlock)
락이 풀릴 때까지 루프로 락 변수를 계속 확인하며 기다리는 방식의 락. 대기가 짧을 때는 빠르지만 기다리는 동안에도 CPU를 계속 쓴다.
뮤텍스(Mutex)
동시에 하나의 스레드만 가질 수 있는 락. 얻지 못하면 스레드를 재워 CPU를 양보하고, 해제되면 OS가 깨우는 구현이 일반적이다.
컨텍스트 스위치(context switch)
CPU에서 실행할 프로세스나 스레드를 전환하는 것. 레지스터와 프로그램 카운터 등의 상태를 저장·복원하며 프로세스 전환에서는 주소 공간도 바뀐다.
원자적 명령어(atomic instruction)
메모리의 읽기와 쓰기를 도중에 다른 코어가 끼어들 수 없는 하나의 동작으로 수행하는 명령. CAS, 페치 앤드 애드, LR/SC 등.
우선순위 역전(Priority Inversion)
낮은 태스크가 가진 자원을 높은 태스크가 기다리는 동안 중간 태스크가 낮은 태스크를 멈추게 하고 계속 실행되어, 높은 태스크가 오래 기다리게 되는 현상.

관련 비교