운영체제 락 개념과 동기화 기법 (3. 고급 락(Spinlock & Semaphore) / 스핀락(Spinlock)과 세마포어(Semaphore))

2025. 2. 26. 12:25정보기술/운영체제 (OS)

📖 스핀락(Spinlock)과 세마포어(Semaphore)

운영체제에서 멀티스레딩(Multithreading) 환경에서는 공유 자원(Shared Resource)에 대한 동기화(Synchronization) 가 필수적입니다.
스핀락(Spinlock)세마포어(Semaphore) 는 동기화를 위한 중요한 기법으로, 각각의 목적과 사용 방식이 다릅니다.


✅ 스핀락(Spinlock)

🔹 스핀락이란?

  • 락(🔒)이 해제될 때까지 CPU가 계속 바쁜 대기(Busy-Waiting)하면서 반복적으로 확인하는 방식.
  • pthread_spinlock_t을 사용하여 구현.
  • 빠른 락 해제가 예상되는 경우 뮤텍스(Mutex)보다 성능이 좋을 수 있음.
  • 멀티코어 CPU 환경에서 커널 락과 같은 저수준 동기화에서 자주 사용.

🔹 스핀락의 특징

특징  설명
Busy-Waiting Lock을 해제할 때까지 계속 CPU를 소모하면서 대기
빠른 락 해제 Lock을 짧은 시간 동안만 유지하는 경우 효과적
멀티코어 환경 여러 개의 CPU가 동시에 실행되는 환경에서 적합
비효율적인 CPU 사용 Lock이 장시간 유지되면 CPU를 낭비하게 됨

✅ C 코드 예제 (Spinlock 적용)

스핀락을 사용하여 하나의 스레드만 공유 자원에 접근하도록 제한하는 예제입니다.

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_spinlock_t spinlock; // 스핀락 선언
int shared_data = 0; // 공유 자원

void* task(void* arg) {
    printf("스레드 %ld: 락 획득 시도 중...\n", (long)arg);
    pthread_spin_lock(&spinlock); // 🔒 스핀락 획득
    printf("스레드 %ld: 락 획득! 작업 수행 중...\n", (long)arg);
    sleep(1); // 작업 수행 (1초)
    shared_data++; // 공유 데이터 변경
    pthread_spin_unlock(&spinlock); // 🔓 스핀락 해제
    printf("스레드 %ld: 락 해제 완료.\n", (long)arg);
    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE); // 스핀락 초기화

    pthread_create(&t1, NULL, task, (void*)1);
    pthread_create(&t2, NULL, task, (void*)2);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_spin_destroy(&spinlock); // 스핀락 제거
    return 0;
}

✅ 실행 결과

스레드 1: 락 획득 시도 중...
스레드 1: 락 획득! 작업 수행 중...
스레드 1: 락 해제 완료.
스레드 2: 락 획득 시도 중...
스레드 2: 락 획득! 작업 수행 중...
스레드 2: 락 해제 완료.
  • 한 번에 하나의 스레드만 락을 획득하여 공유 자원을 수정.
  • 빠른 락 해제 시 효율적이지만, 락이 오래 유지되면 CPU 리소스를 낭비할 수 있음.

✅ 스핀락 사용 시 주의할 점

  1. CPU 과부하 문제
    • 스핀락은 Lock이 해제될 때까지 계속 CPU를 사용하기 때문에 장시간 Lock이 유지되면 CPU 낭비가 발생할 수 있음.
    • 해결책: 짧은 시간 동안만 Lock을 유지하는 경우에만 사용.
  2. 뮤텍스와의 차이점
    • 뮤텍스는 Lock이 해제될 때까지 스레드를 대기 상태로 전환하여 CPU를 절약하지만,
      스핀락은 계속 CPU를 사용하면서 Lock을 확인함.
    • 멀티코어 환경에서는 스핀락이 더 유리할 수 있음.
  3. 단일 코어 환경에서는 비효율적
    • 단일 코어 환경에서는 스핀락이 다른 스레드가 Lock을 해제할 기회를 주지 않기 때문에 비효율적임.

✅ 세마포어(Semaphore)

🔹 세마포어란?

  • N개의 스레드가 동시에 공유 자원에 접근할 수 있도록 제한하는 동기화 기법.
  • sem_t을 사용하여 구현.
  • 뮤텍스(Mutex)는 하나의 스레드만 Lock을 획득할 수 있지만, 세마포어는 count 값에 따라 여러 개의 스레드가 접근 가능.

🔹 세마포어의 특징

특징 설명
여러 개의 스레드 접근 가능 count 값에 따라 N개의 스레드가 동시에 Lock을 획득 가능
뮤텍스와 차이점 뮤텍스는 1개 스레드만 접근 가능, 세마포어는 여러 개의 스레드가 접근 가능
POSIX 세마포어 사용 sem_init(), sem_wait(), sem_post() 활용
데드락 방지 가능 세마포어 카운트를 조절하여 특정 자원에 대한 접근을 제어 가능

✅ C 코드 예제 (Semaphore 적용)

여러 개의 스레드가 동시에 실행될 수 있지만, 최대 2개의 스레드만 공유 자원에 접근할 수 있도록 제한하는 예제입니다.

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

sem_t semaphore; // 세마포어 선언

void* worker(void* arg) {
    sem_wait(&semaphore); // 🔒 세마포어 감소 (자원 획득)
    printf("Worker %ld 실행 중...\n", (long)arg);
    sleep(2); // 작업 수행 (2초)
    printf("Worker %ld 종료.\n", (long)arg);
    sem_post(&semaphore); // 🔓 세마포어 증가 (자원 반납)
    return NULL;
}

int main() {
    pthread_t threads[5];

    sem_init(&semaphore, 0, 2); // 최대 2개 스레드만 접근 가능

    for (long i = 0; i < 5; i++) {
        pthread_create(&threads[i], NULL, worker, (void*)i);
    }

    for (int i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }

    sem_destroy(&semaphore); // 세마포어 제거
    return 0;
}

✅ 실행 결과

Worker 0 실행 중...
Worker 1 실행 중...
(2초 후)
Worker 0 종료.
Worker 1 종료.
Worker 2 실행 중...
Worker 3 실행 중...
(2초 후)
Worker 2 종료.
Worker 3 종료.
Worker 4 실행 중...
(2초 후)
Worker 4 종료.
  • 최대 2개의 스레드만 동시에 실행됨.
  • 다른 스레드는 sem_wait()에서 대기하고 있다가, sem_post()가 호출되면 실행됨.

✅ 세마포어 사용 시 주의할 점

  1. 세마포어 카운트 설정 주의
    • 너무 낮게 설정하면 병목 현상 발생, 너무 높으면 동기화 효과가 낮아짐.
    • 사용 환경에 따라 적절한 값 설정이 필요.
  2. 데드락(Deadlock) 주의
    • sem_wait()을 호출한 스레드가 sem_post()를 호출하지 않으면 세마포어가 영원히 해제되지 않음.
    • 항상 sem_post()를 호출하도록 보장해야 함.

✅ 정리

동기화 기법 특징  사용 예시
스핀락(Spinlock) Lock이 해제될 때까지 바쁜 대기(Busy-Waiting) 커널 락, 짧은 시간 동안 Lock 유지 필요할 때
세마포어(Semaphore) N개의 스레드가 동시에 접근 가능 데이터베이스 연결 제한, 네트워크 요청 동기화