프로세스 동기화 (Process Synchronization)

여러 프로세스가 공유하는 자원의 일관성을 유지하기 위한 메커니즘.
컴퓨터 시스템에서 여러 프로세스가 공유 자원에 접근할 때 충돌을 방지하고 데이터의 일관성을 유지하기 위해 동기화가 필요하다.

다음 두 가지 목적을 가진다:

  1. 실행 순서 제어: 프로세스를 올바른 순서대로 실행하기
  2. 상호 배제: 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기

필요성

  1. 데이터 일관성 유지: 여러 프로세스가 공유 데이터에 동시 접근할 때 발생할 수 있는 예상치 못한 결과를 방지한다.
  2. 실행 순서 보장: 특정 프로세스의 실행이 다른 프로세스의 결과에 의존하는 경우, 올바른 순서로 실행되도록 한다.

임계 영역 문제

임계 영역(Critical Section)은 여러 프로세스가 공유하는 데이터를 접근하는 코드 영역을 말한다.
예를 들어, 은행 계좌의 잔액을 수정하는 코드가 임계 영역이 될 수 있다.

임계 영역 문제를 해결하기 위해서는 다음 세 가지 조건을 만족해야 한다:

  1. 상호 배제(Mutual Exclusion)
    한 번에 하나의 프로세스만 임계 영역에 진입할 수 있다.

  2. 진행(Progress)
    임계 영역에 들어가려는 프로세스의 선택은 무한정 미루어질 수 없다.

  3. 한정 대기(Bounded Waiting)
    프로세스가 임계 영역에 진입하기 위해 대기하는 시간은 유한해야 한다.

주요 동기화 도구들

  1. 뮤텍스(Mutex)
    뮤텍스는 가장 기본적인 동기화 도구로, 상호 배제를 보장한다.
    다음은 뮤텍스를 사용하는 예시 코드:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    void* critical_section(void* arg) {
        pthread_mutex_lock(&mutex);    // 임계 영역 진입 전 잠금
    
        // 임계 영역 코드
        shared_resource++;    // 공유 자원 접근
    
        pthread_mutex_unlock(&mutex);  // 임계 영역 종료 후 잠금 해제
        return NULL;
    }
    
  2. 세마포어(Semaphore)
    세마포어는 여러 개의 프로세스가 공유 자원에 접근할 수 있도록 하는 카운팅 메커니즘:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    sem_t semaphore;
    
    void* resource_access(void* arg) {
        sem_wait(&semaphore);    // 세마포어 획득
    
        // 자원 사용
        use_shared_resource();
    
        sem_post(&semaphore);    // 세마포어 반환
        return NULL;
    }
    
  3. 조건 변수(Condition Variables)
    조건 변수는 특정 조건이 만족될 때까지 프로세스를 대기시키는 메커니즘:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t condition = PTHREAD_COND_INITIALIZER;
    
    void* wait_for_condition(void* arg) {
        pthread_mutex_lock(&mutex);
    
        while (!condition_met) {
            pthread_cond_wait(&condition, &mutex);
        }
    
        // 조건이 만족된 후의 코드
    
        pthread_mutex_unlock(&mutex);
        return NULL;
    }
    

고전적인 동기화 문제들과 해결책

  1. 생산자-소비자 문제
    제한된 버퍼를 공유하는 생산자와 소비자 프로세스 간의 동기화 문제:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    #define BUFFER_SIZE 5
    sem_t empty, full;
    pthread_mutex_t mutex;
    
    void* producer(void* arg) {
        while (1) {
            item = produce_item();
            sem_wait(&empty);           // 빈 공간 대기
            pthread_mutex_lock(&mutex);
    
            // 버퍼에 아이템 추가
    
            pthread_mutex_unlock(&mutex);
            sem_post(&full);            // 채워진 공간 신호
        }
    }
    
  2. 철학자의 만찬 문제
    자원 할당과 교착상태 방지를 다루는 고전적인 문제:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    #define N 5
    pthread_mutex_t chopstick[N];
    
    void* philosopher(void* num) {
        int i = *(int*)num;
    
        while (1) {
            // 왼쪽 젓가락 집기
            pthread_mutex_lock(&chopstick[i]);
            // 오른쪽 젓가락 집기
            pthread_mutex_lock(&chopstick[(i + 1) % N]);
    
            eat();
    
            // 젓가락 내려놓기
            pthread_mutex_unlock(&chopstick[i]);
            pthread_mutex_unlock(&chopstick[(i + 1) % N]);
    
            think();
        }
    }
    
  3. 읽기-쓰기 문제
    여러 읽기 프로세스와 쓰기 프로세스 간의 동기화를 다루는 문제:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
    
    void* reader(void* arg) {
        pthread_rwlock_rdlock(&rwlock);    // 읽기 잠금
    
        // 데이터 읽기
    
        pthread_rwlock_unlock(&rwlock);
        return NULL;
    }
    
    void* writer(void* arg) {
        pthread_rwlock_wrlock(&rwlock);    // 쓰기 잠금
    
        // 데이터 쓰기
    
        pthread_rwlock_unlock(&rwlock);
        return NULL;
    }
    

동기화 관련 주의사항

  1. 데드락(Deadlock) 방지

    • 자원 할당 순서 정하기
    • 타임아웃 설정
    • 데드락 감지 및 복구 메커니즘 구현
  2. 성능 최적화

    • 락의 범위를 최소화
    • 세밀한 락킹 전략 사용
    • 락 프리 알고리즘 고려
  3. 동기화 오버헤드 관리

    • 적절한 동기화 기법 선택
    • 불필요한 동기화 제거
    • 락 경합 최소화

실제 응용 사례

  1. 데이터베이스 시스템
  • 트랜잭션 격리 수준 구현
  • 동시성 제어
  • 데이터 일관성 유지
  1. 운영체제
  • 프로세스 스케줄링
  • 메모리 관리
  • 파일 시스템 접근
  1. 멀티스레드 애플리케이션
  • GUI 이벤트 처리
  • 백그라운드 작업 관리
  • 네트워크 통신

참고 및 출처