PromleeBlog
sitemap
aboutMe

posting thumbnail
동기화 예제 살펴보기 - 하루 10분 운영체제 7일차
Exploring Synchronization Examples - 10 Minutes of OS Day 7

📅

🚀

들어가기 전에 🔗

철학자들이 포크를 두고 식사를 하는 모습
철학자들이 포크를 두고 식사를 하는 모습
썸네일은 철학자들이 포크를 두고 식사를 하는 모습을 gpt에게 그려달라고 한 결과물입니다(포크가 식탁 중앙에 묘사됐네요 ㅋㅋㅋ). 식사하는 철학자 문제는 동기화 문제 중에서도 유명한 문제입니다. 이번 포스팅은 이런 동기화 문제들을 예제 중심으로 살펴보겠습니다.
기술 면접에서는 단순한 개념보다
어떤 상황에서 어떤 도구를 써야 하는지
를 물어보는 경우가 많습니다. 실전 예제를 통해 이러한 상황에 익숙해지는 것이 중요합니다.

🚀

고전적인 동기화 문제들 🔗

생산자-소비자 문제 (Producer-Consumer) 🔗

하나의 공유 버퍼를 두고, 생산자는 데이터를 추가하고 소비자는 데이터를 꺼내는 문제입니다. 버퍼가 가득 찼거나 비어 있을 경우, 적절히 대기해야 합니다.
해결 방법: 세마포어 2개와 뮤텍스를 사용해 생산자와 소비자가 교대로 동작하도록 설계합니다.
C 코드
sem_t empty, full;
pthread_mutex_t mutex;
 
void producer() {
  while (1) {
    // 데이터 생성
    sem_wait(&empty); // 버퍼가 비어있는지 확인
    pthread_mutex_lock(&mutex); // 버퍼에 접근하기 위한 락
    // 데이터 추가
    pthread_mutex_unlock(&mutex); // 락 해제
    sem_post(&full); // 버퍼가 채워진 것을 알림
  }
}
 
void consumer() {
  while (1) {
    sem_wait(&full); // 버퍼가 채워진 것을 확인
    pthread_mutex_lock(&mutex); // 버퍼에 접근하기 위한 락
    // 데이터 소비
    pthread_mutex_unlock(&mutex); // 락 해제
    sem_post(&empty); // 버퍼가 비어있는 것을 알림
  }
}

식사하는 철학자 문제 (Dining Philosophers) 🔗

5명의 철학자가 원형 식탁에서 철학자들 사이에 하나씩 놓여져 있는 포크를 양 손 모두 확보해야 식사를 할 수 있는 상황에서 발생하는 동기화 문제입니다. 각 철학자는 왼쪽과 오른쪽 포크를 모두 확보해야 식사를 시작할 수 있습니다.
문제점: 모든 철학자가 동시에 한쪽 포크만 들고 기다리면 교착 상태가 발생할 수 있습니다.
해결 방법: 홀수/짝수 번호에 따라 포크를 드는 순서를 다르게 하거나, 전체 철학자 수를 제한하는 등의 전략을 사용합니다.

🚀

커널 안에서의 동기화 🔗

운영체제 내부에서도 동기화는 필수입니다. 커널은 여러 프로세스가 시스템 콜을 통해 동시에 접근할 수 있기 때문입니다.
예를 들어 파일 시스템에서는 동일한 파일을 여러 프로세스가 동시에 수정하려고 할 경우,
락(lock)
을 걸어 순서를 조정합니다.
해결 방법: 스핀락(spinlock), 리더-라이터 락(reader-writer lock) 등이 사용됩니다.

스핀락(spinlock) 🔗

락이 해제될 때까지 반복(Spin)하면서 기다리는 락
락을 획득하지 못한 스레드는 CPU를 양보하지 않고 계속 루프를 돌며(
lock을 시도하며
) 기다림
C 코드
while (__sync_lock_test_and_set(&lock, 1)) {
  // 락이 해제될 때까지 계속 대기 (spin)
}

리더-라이터 락(reader-writer lock) 🔗

읽기(read)와 쓰기(write)를 분리해서 제어하는 락
여러 개의 reader가 동시에 접근 가능 (읽기 작업은 병렬 가능)
writer는 단독으로 접근 (읽기/쓰기 둘 다 막고 접근)
C 코드
pthread_rwlock_t lock;
pthread_rwlock_rdlock(&lock);  // 읽기 락
// 읽기 작업 수행
pthread_rwlock_unlock(&lock);  // 락 해제
 
pthread_rwlock_wrlock(&lock);  // 쓰기 락
// 쓰기 작업 수행
pthread_rwlock_unlock(&lock);  // 락 해제

🚀

POSIX 스레드 동기화 예시 🔗

POSIX란?
POSIX는 Portable Operating System Interface의 약자로, 표준화된 운영체제 인터페이스를 제공하는 프로젝트입니다. POSIX 표준은 다양한 운영체제에서 일관된 동작을 보장하기 위해 설계되었습니다. 우리가 사용하는 리눅스, macos, windows 등 대부분의 운영체제가 POSIX 표준을 따르고 있습니다.
POSIX 환경에서는 pthread 라이브러리를 통해 뮤텍스와 세마포어를 사용할 수 있습니다.
pthread_mutex_t lock;
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock);
// 임계구역 코드
pthread_mutex_unlock(&lock);
세마포어 예시
sem_t sem;
sem_init(&sem, 0, 3); // 동시 접근 가능 횟수: 3
sem_wait(&sem);
// 공유 자원 접근
sem_post(&sem);

🚀

Java에서의 동기화 🔗

Java는 synchronized 키워드로 간단하게 임계구역을 정의할 수 있습니다.
public synchronized void increment() {
  count++;
}
또는 명시적으로 락 객체를 사용하는 방법도 있습니다:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
  // 임계구역
} finally {
  lock.unlock();
}

🚀

대체 방안들 🔗

동기화 문제를 완전히 피하기는 어렵지만, 구조적인 대안을 통해 위험을 줄일 수 있습니다
이러한 기법들은 고성능 시스템에서 점점 더 많이 사용되고 있습니다.

🚀

요약 🔗

이번 글에서는 대표적인 동기화 문제들과, 이를 해결하는 구체적인 코드 예제들을 중심으로 정리해보았습니다. 단순히 개념을 외우기보다는
왜 문제가 생기고, 어떤 도구가 적합한지를 파악하는 능력
이 중요합니다.
다음 글에서는 병목 현상과 교착 상태를 다루는
교착 상태(Deadlock)
개념을 자세히 알아보겠습니다.

참고 🔗