개요

<aside> 🥕

상황 1 : 프로세스 A가 공유 메모리 공간에 데이터를 쓰고, 프로세스 B가 해당 메모리 공간을 읽는 상황 상황 2 : 같은 프로세스의 자원을 공유하는 스레드 간 통신이 이뤄지는 상황

</aside>

동시다발적으로 실행되는 프로세스 혹은 스레드를 다룰 때는, 언제나 임계 구역을 동시에 실행하지 않도록 유의해야함. 프로세스 혹은 스레드가 동시에 임계 구역 코드를 실행하여 문제가 발생하는 상황을 **레이스 컨디션(race condition)**이라함. 레이스 컨디션 발생 시 자원 일관성이 손상될 수 있기에 2개 이상의 프로세스 혹은 스레드가 임계 영역에 진입하고자 한다면 둘 중 하나는 작업이 끝날 때까지 대기해야함(레이스 컨디션은 소스 코드 상에서 발생 가능한 문제 상황)

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

int shared_data = 0 // 공유 데이터

void* increment(void* arg)
{
	int i;
	
	for(i=0; i<100000; i++)
	{
		shared_data += 1;
	}
	
	return NULL;
}

void* decrement(void* arg)
{
	int i;
	for(i=0; i<100000; i++)
	{
		shared_data -= 1;
	}
	
	return NULL;
}

int main()
{
	pthread_t thread1, thread2;
	
	pthread_create(&thread1, NULL, increment, NULL);
	pthread_create(&thread2, NULL, decrement, NULL);
	
	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);
	
	printf("공유 변수 마지막 결과 : %d\\n", shared_data);
	
	return 0;
}

실행마다 결과가 다르게 나옴을 알 수 있음 : 레이스 컨디션이 발생했다는 의미

실행마다 결과가 다르게 나옴을 알 수 있음 : 레이스 컨디션이 발생했다는 의미

레이스 컨디션을 방지하면서 임계 구역을 관리하려면, 프로세스와 스레드가 **동기화(synchronization)**되어야함. 동기화는 다음 2가지 조건을 준수하면서 실행하는 것을 의미 :

  1. 실행 순서 제어 : 프로세스 및 스레드를 올바른 순서로 실행하기
  2. 상호 배제 : 동시에 접근하면 안 되는 자원(공유 자원)에 하나의 프로세스 및 스레드만 접근하기

<aside> 🥕

즉, 동기화는 “실행 순어 제어를 위한 동기화”가 있고 “상호 배제를 위한 동기화”가 존재

</aside>

실행 순서 제어 동기화(예시)

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

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int shared_resource = 0;
int resource_available = 0;
int turn = 0; // 소비자 차례 제어 변수

void* producer(void* arg)
{
    pthread_mutex_lock(&lock);

    shared_resource = 42; // 공유 자원 생성
    resource_available = 1;
    turn = 0; // 첫 소비자 차례
    pthread_cond_broadcast(&cond); // 소비자에게 알림

    pthread_mutex_unlock(&lock);
    return NULL;
}

void* consumer(void* arg)
{
    int id = *(int *)arg;

    pthread_mutex_lock(&lock);

    while (!resource_available || turn != id) {
        pthread_cond_wait(&cond, &lock); // 자원이 준비될 때까지 대기
    }
    printf("Consumed: %d by consumer %d\\n", shared_resource, id); // 공유 자원 소비
    turn++; // 다음 소비자에게 차례 넘기기

    pthread_cond_broadcast(&cond); // 모든 대기 중인 소비자에게 알림
    pthread_mutex_unlock(&lock);

    return NULL;
}

int main()
{
    pthread_t producer_thread;
    pthread_t consumers[3];
    int consumer_ids[3] = {0, 1, 2};

    // 스레드 생성
    for (int i = 0; i < 3; i++) {
        pthread_create(&consumers[i], NULL, consumer, (void *)&consumer_ids[i]);
    }
    pthread_create(&producer_thread, NULL, producer, NULL);

    // 스레드 종료 대기
    pthread_join(producer_thread, NULL);
    for (int i = 0; i < 3; i++) {
        pthread_join(consumers[i], NULL);
    }

    return 0;
}

여러 스레드를 생성해 공유 자원에 접근하지만, 순서 제어를 통해 순서대로 접근하도록 만듦

여러 스레드를 생성해 공유 자원에 접근하지만, 순서 제어를 통해 순서대로 접근하도록 만듦

상호 배제 동기화(예시)

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

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

int shared_counter = 0;

void* increment(void* arg)
{
    for(int i=0; i<100000; i++)
    {
        pthread_mutex_lock(&lock); // 임계 구역 시작
        shared_counter++;
        pthread_mutex_unlock(&lock); // 임계 구역 종료
    }

    return NULL;
}

int main()
{
    pthread_t t1, t2;

    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);

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

    printf("Final counter value: %d\\n", shared_counter);
    return 0;
}

원래라면 공유 자원에 동시 접근하므로 레이스 컨디션이 발생해 값이 일정하게 20000이 안 나왔지만, 상호 배제 동기화를 통해 20000이 출력되는 것을 보장하게 됨

원래라면 공유 자원에 동시 접근하므로 레이스 컨디션이 발생해 값이 일정하게 20000이 안 나왔지만, 상호 배제 동기화를 통해 20000이 출력되는 것을 보장하게 됨

동기화 기법

뮤텍스 락(mutex lock)