슬립락: CPU를 양보하는 Block & Wakeup 동기화

슬립락의 Block & Wakeup 동작, 스핀락과의 CPU 사용 차이, 문맥 교환 비용과 적합한 임계 영역을 정리한다.

2026-08-14 · 최초 발행 2025-12-25

락을 기다리는 스레드를 대기 큐로 보낼 때

슬립락(Sleep Lock)은 이미 점유된 락을 요청한 프로세스나 스레드를 휴면 상태로 전환하는 동기화 메커니즘이다. Busy Waiting으로 CPU를 계속 소비하는 대신, Block & Wakeup 방식으로 CPU를 다른 작업에 넘긴다. 대기자는 운영체제 스케줄러의 대기 큐에서 관리되고, 락이 풀리면 다시 실행 가능한 상태가 된다.

뮤텍스와 세마포어의 Block-Wakeup 구현이 대표적인 형태다. 문맥 교환 비용은 발생하지만, 임계 영역이 길다면 CPU를 비워 두지 않는 편이 더 낫다.

NoYes스레드Lock획득?휴면 상태(Blocked)대기Critical SectionUnlock대기 스레드깨우기

Block과 Wakeup이 이어지는 과정

락이 잠겨 있으면 요청 스레드는 대기 큐에 들어간 뒤 block()으로 휴면 상태가 된다. 이 순간 CPU는 다른 프로세스를 실행할 수 있다. 락을 가진 쪽이 unlock하면 대기 큐에서 스레드를 꺼내 wakeup()하고, 깨어난 스레드는 다시 락 획득을 시도한다.

typedef struct {
    int locked;
    queue_t wait_queue;  // 대기 큐
} sleep_lock_t;

void sleep_lock(sleep_lock_t *lock) {
    while (lock->locked) {
        enqueue(&lock->wait_queue, current_thread);
        block();  // 휴면 상태로 전환
        // 여기서 CPU 양보, 다른 프로세스 실행
    }
    lock->locked = 1;
}

void sleep_unlock(sleep_lock_t *lock) {
    lock->locked = 0;
    if (!is_empty(&lock->wait_queue)) {
        thread_t *t = dequeue(&lock->wait_queue);
        wakeup(t);  // 대기 스레드 깨움
    }
}

실행 상태는 Ready에서 Running으로, 락 획득에 실패하면 Blocked로 이동한다. wakeup() 이후에는 다시 Ready 상태에 놓이고 스케줄링을 기다린다.

스케줄됨Lock 획득 실패 (block())Lock 해제 (wakeup())종료ReadyRunningBlocked휴면 상태CPU 미사용대기 큐에서 관리

대기 큐와 스케줄러를 포함해 보면 흐름은 다음과 같다.

"스레드 2""스케줄러""대기 큐""Sleep Lock""스레드 1(Lock 보유)""스레드 2""스케줄러""대기 큐""Sleep Lock""스레드 1(Lock 보유)"Lock 획득Critical SectionLock 시도실패 (이미 Lock됨)대기 큐에 추가block() 호출Blocked 상태다른 스레드 스케줄Unlock대기 큐에서 T2 제거wakeup(T2)Ready 상태스케줄됨Lock 획득Critical Section

스핀락과 갈리는 지점

슬립락은 대기 중 Blocked 상태에 머물며 CPU를 양보한다. 반대로 스핀락은 Running 상태에서 락을 반복 확인하는 Busy Waiting을 수행한다. 이 차이는 응답 속도와 문맥 교환 비용, 전력 효율의 선택으로 이어진다.

항목 슬립락 (Sleep Lock) 스핀락 (Spin Lock)
대기 방식 Block & Sleep Busy Waiting
상태 Blocked (휴면) Running (활성)
CPU 사용 0% (양보) 100% (점유)
문맥 교환 발생 (2회) 미발생
스케줄링 대기 큐 관리 미개입
응답 속도 느림 빠름
적합한 경우 긴 임계 영역 짧은 임계 영역
전력 효율 높음 낮음
0153045607590105120Lock 획득 대기 (100% CPU) Lock 획득 시도 휴면 (0% CPU) Critical Section Wakeup 및 재개 Critical Section 스핀락슬립락CPU 시간 사용 비교

문맥 교환은 슬립락의 비용이고 CPU 절약은 그 대가로 얻는 이점이다. 스핀락은 문맥 교환이 없지만, 대기하는 동안 CPU가 무용지물이 될 수 있다.

슬립락문맥 교환오버헤드CPU 절약수천 사이클다른 작업 가능스핀락문맥 교환없음CPU 낭비0 사이클대기 무용지물

임계 영역이 짧음(< 1000 사이클)이면 스핀락이 유리하다. 1000~10000 사이클 구간은 상황에 따라 달라지며, 김(> 10000 사이클)이라면 슬립락이 유리하다.

CPU를 비우는 대신 드는 비용

대기 스레드를 휴면으로 전환하면 CPU는 다른 작업을 실행할 수 있다. 멀티태스킹 효율과 배터리 사용 측면에서 유리하며, 모바일·임베디드 시스템처럼 전력 제약이 있는 환경에도 맞는다.

Lock 대기휴면 상태CPU 양보다른 프로세스실행 가능시스템 전체처리량 증가

스케줄러가 대기자를 관리하므로 우선순위 역전 문제를 완화해 Hang을 막고, 공정한 자원 분배를 기대할 수 있다. 대기 프로세스 수와 무관하게 안정적으로 동작하는 점도 장점이다. 파일 읽기·쓰기나 네트워크 통신 같은 I/O 대기, 수천~수만 사이클이 걸리는 복잡한 계산, 데이터베이스 트랜잭션 처리가 긴 임계 영역의 예다.

다만 Block과 Wakeup에는 각각 레지스터 저장 또는 복원, 상태 변경, 스케줄링이 필요하다.

단계 작업 비용 (사이클)
Block 레지스터 저장, 상태 변경, 스케줄링 ~1000
Wakeup 레지스터 복원, 상태 변경, 스케줄링 ~1000
총 오버헤드 ~2000

락 해제 직후 즉시 실행되지 않을 수 있고, 문맥 교환은 캐시 미스를 늘릴 수 있다. 대기 큐를 관리하는 비용도 함께 고려해야 한다. 실제 작업이 매우 짧다면 이 비용이 임계 영역 자체보다 커진다.

// 비효율적 사용 예시
mutex_lock(&lock);   // 문맥 교환 (~1000 사이클)
counter++;           // 실제 작업 (1 사이클)
mutex_unlock(&lock); // 문맥 교환 (~1000 사이클)
// 총 ~2001 사이클, 오버헤드 > 99.9%!

사용할 수 있는 컨텍스트와 피해야 할 곳

슬립락은 사용자 공간 애플리케이션, 파일 I/O·네트워크·데이터베이스처럼 긴 임계 영역, 모바일·IoT·임베디드 환경, CPU 효율이 중요한 단일 코어, 많은 프로세스가 경쟁하는 상황에 적합하다.

반대로 스케줄러 내부처럼 재귀 호출 위험이 있는 커널 임계 구역에서는 맞지 않는다. 인터럽트 핸들러처럼 Sleep할 수 없는 컨텍스트, 수십~수백 사이클로 끝나는 짧은 임계 영역, 엄격한 응답 시간 제약이 있는 실시간 요구도 피해야 한다.

POSIX Mutex는 Block & Sleep 방식으로 긴 임계 영역을 보호하는 데 사용할 수 있다.

pthread_mutex_t mutex;

void* thread_func(void* arg) {
    pthread_mutex_lock(&mutex);  // Block & Sleep
    // 긴 Critical Section (I/O, 복잡한 계산)
    process_large_data();
    pthread_mutex_unlock(&mutex);
    return NULL;
}

조건 변수는 생산자-소비자 구조에서 버퍼가 비었을 때 소비자를 휴면 대기시킬 수 있다.

pthread_mutex_lock(&mutex);
while (buffer_empty) {
    pthread_cond_wait(&cond, &mutex);  // 휴면 대기
}
item = consume_from_buffer();
pthread_mutex_unlock(&mutex);

일부 시스템은 먼저 짧게 스핀하고, 획득하지 못하면 슬립락으로 전환하는 하이브리드 접근을 사용한다.

// 짧은 시간 스핀 후 슬립
void adaptive_lock(lock_t *lock) {
    int spin_count = 0;
    const int MAX_SPIN = 100;

    // 먼저 스핀 시도
    while (spin_count < MAX_SPIN) {
        if (try_lock(lock))
            return;
        spin_count++;
    }

    // 실패 시 슬립
    sleep_lock(lock);
}
슬립락동기화뮤텍스스핀락운영체제