C 언어에서 스레드 간 데이터 공유와 충돌 방지 방법

멀티스레드 프로그래밍은 현대 소프트웨어 개발에서 필수적인 기술입니다. 특히, 여러 스레드가 동시에 동일한 데이터를 읽거나 수정해야 하는 경우, 데이터 충돌이나 일관성 문제를 방지하는 것이 중요합니다. C 언어는 강력한 성능과 유연성을 제공하지만, 스레드 간 데이터 공유를 적절히 처리하지 않으면 예기치 않은 오류가 발생할 수 있습니다. 본 기사에서는 C 언어로 멀티스레드 환경을 구축하는 기본 개념부터 데이터 충돌을 방지하는 다양한 전략까지 다룹니다. 이를 통해 멀티스레드 프로그램의 안정성과 효율성을 극대화할 수 있습니다.

목차

스레드와 데이터 공유의 기본 개념


멀티스레드 프로그래밍에서 스레드는 하나의 프로세스 내에서 실행되는 독립적인 실행 단위입니다. 스레드는 동일한 프로세스의 메모리 공간을 공유하므로 전역 변수나 힙 영역의 데이터에 동시에 접근할 수 있습니다.

스레드 간 데이터 공유의 장점

  • 효율성: 메모리를 공유하여 데이터 복사가 필요 없어 성능이 향상됩니다.
  • 공통 데이터 처리: 여러 스레드가 동일한 데이터를 기반으로 작업을 수행할 수 있습니다.

데이터 공유의 도전 과제

  • 데이터 충돌: 동시에 데이터에 접근하거나 수정하면 데이터 손상이나 예기치 않은 결과가 발생할 수 있습니다.
  • 데이터 일관성 유지: 여러 스레드가 데이터를 처리하는 순서를 제어하지 않으면 데이터의 일관성이 깨질 수 있습니다.

공유 데이터 접근 방식

  • 전역 변수: 모든 스레드에서 접근 가능한 변수. 충돌 방지가 필요합니다.
  • 동적 메모리: 힙 영역에서 동적 할당한 데이터로, 메모리 관리를 명확히 해야 합니다.

스레드 간 데이터 공유는 적절한 관리와 설계를 통해 안정성과 성능을 동시에 확보할 수 있습니다.

경쟁 조건과 그 위험성


경쟁 조건은 여러 스레드가 동시에 공유 자원에 접근하거나 수정하려고 할 때 발생하는 상황을 의미합니다. 이는 멀티스레드 환경에서 데이터 충돌의 주요 원인으로, 프로그램의 예상치 못한 동작을 초래할 수 있습니다.

경쟁 조건의 발생 원인

  • 비동기적 실행: 스레드가 독립적으로 실행되며, 실행 순서를 예측할 수 없습니다.
  • 공유 자원 접근: 동일한 메모리 위치에 여러 스레드가 동시에 읽기와 쓰기를 수행합니다.
  • 동기화 부족: 데이터 접근에 대한 적절한 제어가 이루어지지 않을 경우, 결과가 일관되지 않을 수 있습니다.

경쟁 조건의 예시

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

int shared_data = 0;

void* increment(void* arg) {
    for (int i = 0; i < 100000; i++) {
        shared_data++;
    }
    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 value: %d\n", shared_data);
    return 0;
}

출력 문제: 예상 결과는 200,000이지만, 실제로는 그보다 작거나 다른 값이 나올 수 있습니다. 이는 shared_data에 접근하는 두 스레드 간의 경쟁 조건 때문입니다.

경쟁 조건의 위험성

  • 데이터 손상: 공유 자원의 일관성이 깨져 잘못된 데이터를 생성합니다.
  • 디버깅 어려움: 멀티스레드 프로그램의 비결정적 동작으로 인해 오류를 재현하기 어렵습니다.
  • 프로그램 불안정성: 비정상적인 결과가 누적되어 시스템 전체의 안정성을 해칠 수 있습니다.

경쟁 조건을 방지하기 위해서는 적절한 동기화 메커니즘을 통해 데이터 접근을 제어해야 합니다.

뮤텍스와 세마포어로 충돌 방지


뮤텍스(Mutex)와 세마포어(Semaphore)는 멀티스레드 프로그래밍에서 데이터 충돌을 방지하고 스레드 간 동기화를 제공하는 주요 도구입니다. 이들은 공유 자원에 대한 접근을 제어함으로써 데이터 일관성을 유지합니다.

뮤텍스(Mutex)의 개념과 사용


뮤텍스는 Mutual Exclusion의 약자로, 하나의 스레드만 특정 코드 블록 또는 공유 자원에 접근할 수 있도록 보장합니다.

  • 특징: 공유 자원 보호를 위한 잠금(lock)과 해제(unlock) 메커니즘 제공.
  • 사용 사례: 단일 자원에 대해 한 번에 하나의 스레드만 접근해야 할 때.

뮤텍스 사용 예제

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

pthread_mutex_t lock;
int shared_data = 0;

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

int main() {
    pthread_t t1, t2;
    pthread_mutex_init(&lock, NULL);

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

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

    pthread_mutex_destroy(&lock);
    printf("Final value: %d\n", shared_data);
    return 0;
}

결과: 경쟁 조건 없이 예상된 값 출력.

세마포어(Semaphore)의 개념과 사용


세마포어는 자원의 접근을 제어하기 위해 정수 값을 사용하는 동기화 도구입니다.

  • 특징: 한 번에 N개의 스레드가 자원에 접근할 수 있도록 허용.
  • 사용 사례: 제한된 개수의 자원(예: 연결 풀)에 대해 접근을 제어해야 할 때.

세마포어 사용 예제

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

sem_t semaphore;
int shared_data = 0;

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

int main() {
    pthread_t t1, t2;
    sem_init(&semaphore, 0, 1);

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

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

    sem_destroy(&semaphore);
    printf("Final value: %d\n", shared_data);
    return 0;
}

결과: 뮤텍스와 동일하게 경쟁 조건 방지.

뮤텍스와 세마포어의 차이점

  • 뮤텍스: 단일 스레드 접근 보장(이진 잠금).
  • 세마포어: 다중 스레드 접근 가능, 접근 가능한 스레드 수 설정 가능.

뮤텍스와 세마포어는 멀티스레드 환경에서 데이터 충돌 방지와 효율적 동기화를 제공하는 핵심 도구로, 상황에 맞게 적절히 사용해야 합니다.

C 언어의 스레드 라이브러리 활용법


C 언어에서는 POSIX 스레드(Pthreads) 라이브러리를 사용하여 멀티스레드 프로그래밍을 구현할 수 있습니다. 이 라이브러리는 스레드 생성, 관리, 동기화를 위한 다양한 함수와 메커니즘을 제공합니다.

POSIX 스레드의 주요 함수

  1. pthread_create: 새 스레드를 생성합니다.
  • 형식: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • 예제:
   pthread_t thread_id;
   pthread_create(&thread_id, NULL, thread_function, NULL);
  1. pthread_join: 생성된 스레드가 종료될 때까지 대기합니다.
  • 형식: int pthread_join(pthread_t thread, void **retval);
  • 예제:
   pthread_join(thread_id, NULL);
  1. pthread_exit: 스레드의 실행을 종료합니다.
  • 형식: void pthread_exit(void *retval);
  • 예제:
   pthread_exit(NULL);
  1. pthread_mutex_lock / pthread_mutex_unlock: 뮤텍스를 잠그거나 해제합니다.
  • 형식:
    • int pthread_mutex_lock(pthread_mutex_t *mutex);
    • int pthread_mutex_unlock(pthread_mutex_t *mutex);
  1. pthread_cond_wait / pthread_cond_signal: 조건 변수와 함께 사용하여 스레드 간 통신을 처리합니다.
  • 형식:
    • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    • int pthread_cond_signal(pthread_cond_t *cond);

스레드 생성과 동기화 예제


다음은 Pthreads를 사용하여 스레드 생성 및 동기화를 구현한 간단한 코드입니다.

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

void* print_message(void* arg) {
    char* message = (char*)arg;
    printf("%s\n", message);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;

    pthread_create(&thread1, NULL, print_message, "Hello from Thread 1");
    pthread_create(&thread2, NULL, print_message, "Hello from Thread 2");

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

출력:

Hello from Thread 1
Hello from Thread 2

(실행 순서는 스레드 스케줄러에 따라 달라질 수 있습니다.)

동기화를 통한 데이터 충돌 방지


위에서 언급한 뮤텍스와 조건 변수를 사용하면 여러 스레드 간의 데이터 충돌을 방지할 수 있습니다.

POSIX 스레드의 이점

  • 표준화: POSIX 표준을 준수하여 다양한 플랫폼에서 호환성 보장.
  • 성능: 저수준 스레드 관리로 성능 최적화.
  • 확장성: 멀티스레드 애플리케이션 개발에 적합한 다양한 도구 제공.

POSIX 스레드는 멀티스레드 환경을 효과적으로 구성할 수 있는 강력한 도구이며, 이를 활용하면 안정적이고 성능 높은 프로그램을 구현할 수 있습니다.

스레드 안전한 데이터 구조


멀티스레드 환경에서 데이터 구조를 안전하게 사용하는 것은 데이터 일관성과 안정성을 유지하는 데 중요합니다. C 언어에서는 기본 데이터 구조를 스레드 안전하게 만들기 위해 동기화 메커니즘과 추가적인 설계가 필요합니다.

스레드 안전의 정의


스레드 안전(Thread-Safe)이란, 여러 스레드가 동시에 데이터 구조에 접근하거나 수정하더라도 데이터 손상이나 충돌이 발생하지 않는 상태를 의미합니다. 이를 구현하기 위해 주로 뮤텍스, 세마포어, 조건 변수와 같은 동기화 도구를 사용합니다.

스레드 안전한 데이터 구조의 필요성

  • 공유 자원의 일관성 유지: 스레드 간 데이터 수정이 예기치 않은 동작을 초래하지 않도록 보장합니다.
  • 동시성 개선: 효율적인 동기화로 성능을 저하시키지 않으면서 동시에 다수의 스레드가 작업할 수 있습니다.

스레드 안전한 데이터 구조 설계

  1. 링크드 리스트(Linked List)
    링크드 리스트를 스레드 안전하게 만들기 위해 삽입, 삭제, 탐색 등의 작업에서 동기화를 적용해야 합니다.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef struct Node {
    int data;
    struct Node* next;
} Node;

pthread_mutex_t list_lock = PTHREAD_MUTEX_INITIALIZER;
Node* head = NULL;

void insert(int value) {
    pthread_mutex_lock(&list_lock);
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = value;
    new_node->next = head;
    head = new_node;
    pthread_mutex_unlock(&list_lock);
}

void print_list() {
    pthread_mutex_lock(&list_lock);
    Node* current = head;
    while (current) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
    pthread_mutex_unlock(&list_lock);
}
  1. 큐(Queue)
    큐는 동기화 큐(Synchronized Queue)를 사용하여 스레드 안전을 구현합니다.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef struct Queue {
    int* data;
    int front, rear, size, capacity;
    pthread_mutex_t lock;
} Queue;

Queue* create_queue(int capacity) {
    Queue* queue = (Queue*)malloc(sizeof(Queue));
    queue->capacity = capacity;
    queue->front = queue->size = 0;
    queue->rear = capacity - 1;
    queue->data = (int*)malloc(queue->capacity * sizeof(int));
    pthread_mutex_init(&queue->lock, NULL);
    return queue;
}

void enqueue(Queue* queue, int item) {
    pthread_mutex_lock(&queue->lock);
    if (queue->size == queue->capacity) {
        pthread_mutex_unlock(&queue->lock);
        return;
    }
    queue->rear = (queue->rear + 1) % queue->capacity;
    queue->data[queue->rear] = item;
    queue->size++;
    pthread_mutex_unlock(&queue->lock);
}

int dequeue(Queue* queue) {
    pthread_mutex_lock(&queue->lock);
    if (queue->size == 0) {
        pthread_mutex_unlock(&queue->lock);
        return -1;  // Queue is empty
    }
    int item = queue->data[queue->front];
    queue->front = (queue->front + 1) % queue->capacity;
    queue->size--;
    pthread_mutex_unlock(&queue->lock);
    return item;
}
  1. 해시 테이블(Hash Table)
    스레드 안전한 해시 테이블을 구현하려면 각 버킷에 대해 별도의 뮤텍스를 사용하거나, 전체 해시 테이블에 하나의 뮤텍스를 사용하는 방식이 있습니다.

스레드 안전한 데이터 구조의 장점

  • 충돌 방지: 여러 스레드가 데이터 구조를 안전하게 사용할 수 있습니다.
  • 유지보수성 향상: 명확한 동기화 규칙으로 코드 가독성이 좋아지고 디버깅이 용이합니다.

스레드 안전한 데이터 구조를 설계하고 구현하면 멀티스레드 프로그램의 안정성과 성능을 동시에 확보할 수 있습니다.

조건 변수와 스레드 통신


조건 변수(Condition Variable)는 스레드 간 효율적인 통신과 동기화를 가능하게 하는 강력한 도구입니다. 특정 조건이 충족될 때까지 스레드를 대기 상태로 두거나 다른 스레드에 신호를 보내 작업을 재개하도록 할 수 있습니다.

조건 변수의 개념

  • 정의: 조건 변수는 뮤텍스와 함께 사용되어, 특정 조건이 충족될 때까지 스레드를 대기시킵니다.
  • 기능: 스레드 간 데이터를 기반으로 작업 순서를 제어할 수 있습니다.
  • 사용 사례: 생산자-소비자 문제, 스레드 풀 관리 등.

POSIX 조건 변수 함수

  1. pthread_cond_wait
  • 스레드를 대기 상태로 전환하고 뮤텍스의 잠금을 해제합니다.
  • 형식: int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  1. pthread_cond_signal
  • 대기 중인 스레드 하나를 깨웁니다.
  • 형식: int pthread_cond_signal(pthread_cond_t *cond);
  1. pthread_cond_broadcast
  • 모든 대기 중인 스레드를 깨웁니다.
  • 형식: int pthread_cond_broadcast(pthread_cond_t *cond);

조건 변수 사용 예제: 생산자-소비자 문제

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

#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_empty = PTHREAD_COND_INITIALIZER;

void* producer(void* arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);

        while (count == BUFFER_SIZE) {
            pthread_cond_wait(&cond_empty, &mutex);
        }

        buffer[count++] = i;
        printf("Produced: %d\n", i);

        pthread_cond_signal(&cond_full);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);

        while (count == 0) {
            pthread_cond_wait(&cond_full, &mutex);
        }

        int item = buffer[--count];
        printf("Consumed: %d\n", item);

        pthread_cond_signal(&cond_empty);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main() {
    pthread_t prod, cons;

    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);

    pthread_join(prod, NULL);
    pthread_join(cons, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond_full);
    pthread_cond_destroy(&cond_empty);

    return 0;
}

작동 원리

  1. 생산자는 버퍼가 가득 찼을 때 cond_empty 조건 변수를 통해 대기 상태로 들어갑니다.
  2. 소비자는 버퍼가 비어 있을 때 cond_full 조건 변수를 통해 대기 상태로 들어갑니다.
  3. 조건이 충족되면 생산자와 소비자는 서로를 깨우며 작업을 진행합니다.

조건 변수의 장점

  • 효율적인 자원 사용: 불필요한 CPU 사용을 줄이고, 스레드가 필요한 작업을 즉시 처리할 수 있도록 보장합니다.
  • 복잡한 동기화 문제 해결: 생산자-소비자 패턴과 같은 고급 동기화 문제를 간단히 해결할 수 있습니다.

조건 변수는 멀티스레드 환경에서 동기화와 통신을 구현하는 필수 도구로, 데이터 일관성을 유지하며 프로그램의 효율성을 극대화합니다.

데드락과 방지 전략


데드락(Deadlock)은 두 개 이상의 스레드가 서로의 자원 접근을 기다리며 무한히 대기 상태에 빠지는 상황을 의미합니다. 멀티스레드 프로그램에서 데드락은 심각한 문제로, 이를 방지하는 것은 안정적인 시스템 구현에 필수적입니다.

데드락의 발생 조건


데드락은 아래 네 가지 조건이 동시에 충족될 때 발생합니다:

  1. 상호 배제(Mutual Exclusion): 자원은 한 번에 한 스레드만 사용할 수 있습니다.
  2. 점유 및 대기(Hold and Wait): 스레드는 이미 점유한 자원을 유지하면서 추가 자원을 기다립니다.
  3. 비선점(No Preemption): 스레드가 자원을 강제로 빼앗길 수 없습니다.
  4. 순환 대기(Circular Wait): 자원을 점유한 스레드들이 원형으로 서로의 자원을 기다립니다.

데드락 방지 전략


데드락을 방지하기 위해 여러 전략을 사용할 수 있습니다:

1. 자원 획득 순서 지정


모든 스레드가 자원을 획득하는 순서를 정해, 순환 대기를 방지합니다.

  • 방법: 자원에 번호를 부여하고, 항상 작은 번호의 자원부터 요청하도록 합니다.

예제:

pthread_mutex_t lock1, lock2;

// 스레드 A
pthread_mutex_lock(&lock1);
pthread_mutex_lock(&lock2);

// 스레드 B
pthread_mutex_lock(&lock1);
pthread_mutex_lock(&lock2);

2. 타임아웃 설정


스레드가 자원을 일정 시간 동안 획득하지 못하면 요청을 포기하게 합니다.

  • 장점: 데드락 가능성을 줄이면서 자원 대기 시간을 제한합니다.

3. 자원 사전 할당


스레드가 실행되기 전에 필요한 모든 자원을 한 번에 할당받게 합니다.

  • 단점: 자원의 비효율적 사용 가능성.

4. 교착 상태 탐지 및 복구


데드락 발생 여부를 탐지한 후, 특정 스레드를 종료하거나 자원을 강제로 해제합니다.

  • 방법: 자원 대기 그래프를 사용해 순환 대기를 탐지합니다.

데드락 방지를 위한 코드 설계


다음은 자원 획득 순서를 지정하여 데드락을 방지한 예제입니다:

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

pthread_mutex_t lock1, lock2;

void* thread1(void* arg) {
    pthread_mutex_lock(&lock1);
    sleep(1);  // 일부러 지연시켜 데드락 상황을 유발
    pthread_mutex_lock(&lock2);

    printf("Thread 1 acquired both locks\n");
    pthread_mutex_unlock(&lock2);
    pthread_mutex_unlock(&lock1);
    return NULL;
}

void* thread2(void* arg) {
    pthread_mutex_lock(&lock1);
    pthread_mutex_lock(&lock2);

    printf("Thread 2 acquired both locks\n");
    pthread_mutex_unlock(&lock2);
    pthread_mutex_unlock(&lock1);
    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);

    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);

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

    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);

    return 0;
}

결과: 자원 획득 순서를 고정하여 데드락 방지.

데드락 예방의 중요성

  • 프로그램 안정성 향상: 데드락 없는 프로그램은 신뢰성과 안정성을 제공합니다.
  • 성능 최적화: 자원이 효율적으로 관리되므로 성능 저하를 방지합니다.

데드락은 멀티스레드 프로그램의 주요 도전 과제이지만, 적절한 설계와 예방 전략으로 효과적으로 방지할 수 있습니다.

실제 코드 예제


멀티스레드 환경에서 데이터 공유와 충돌 방지를 구현한 실제 코드 예제를 통해, 이론을 실질적으로 적용하는 방법을 살펴봅니다. 다음은 생산자-소비자 문제를 해결하기 위한 코드로, 조건 변수와 뮤텍스를 활용하여 스레드 간 데이터 공유와 동기화를 관리합니다.

문제 설명

  • 생산자: 데이터를 생성하여 버퍼에 추가.
  • 소비자: 버퍼에서 데이터를 꺼내 처리.
  • 문제: 버퍼가 비어 있으면 소비자는 대기해야 하고, 버퍼가 가득 차면 생산자는 대기해야 함.

코드 구현

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

#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_empty = PTHREAD_COND_INITIALIZER;

void* producer(void* arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);

        while (count == BUFFER_SIZE) {
            pthread_cond_wait(&cond_empty, &mutex);
        }

        buffer[count++] = i;
        printf("Produced: %d (Buffer Count: %d)\n", i, count);

        pthread_cond_signal(&cond_full);
        pthread_mutex_unlock(&mutex);

        sleep(1);  // 생산 속도 조절
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);

        while (count == 0) {
            pthread_cond_wait(&cond_full, &mutex);
        }

        int item = buffer[--count];
        printf("Consumed: %d (Buffer Count: %d)\n", item, count);

        pthread_cond_signal(&cond_empty);
        pthread_mutex_unlock(&mutex);

        sleep(2);  // 소비 속도 조절
    }
    return NULL;
}

int main() {
    pthread_t prod, cons;

    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);

    pthread_join(prod, NULL);
    pthread_join(cons, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond_full);
    pthread_cond_destroy(&cond_empty);

    return 0;
}

코드 동작 설명

  1. 뮤텍스: pthread_mutex_lockpthread_mutex_unlock으로 공유 자원 접근 보호.
  2. 조건 변수:
  • pthread_cond_wait로 생산자와 소비자가 조건 충족 시까지 대기.
  • pthread_cond_signal로 조건이 충족되면 대기 중인 스레드 깨움.
  1. 버퍼 관리: buffer 배열을 사용해 생산자와 소비자 간 데이터를 공유.

실행 결과 예시

Produced: 0 (Buffer Count: 1)
Produced: 1 (Buffer Count: 2)
Consumed: 0 (Buffer Count: 1)
Produced: 2 (Buffer Count: 2)
Consumed: 1 (Buffer Count: 1)
Produced: 3 (Buffer Count: 2)
Consumed: 2 (Buffer Count: 1)
...

코드 특징 및 개선점

  • 특징: 생산자와 소비자의 작업이 충돌 없이 동기화되어 실행됩니다.
  • 개선점:
  • 생산량과 소비량을 조정하여 더 복잡한 시나리오를 시뮬레이션 가능.
  • 더 큰 버퍼나 동적 메모리 할당을 통해 확장성 추가.

이 코드는 멀티스레드 환경에서 데이터 공유와 충돌 방지를 구현한 간단한 예제이며, 실제 개발에서 유사한 문제를 해결하는 데 응용할 수 있습니다.

요약


C 언어에서의 멀티스레드 프로그래밍은 데이터 공유와 충돌 방지의 적절한 관리를 통해 안정성과 효율성을 확보할 수 있습니다. 뮤텍스, 세마포어, 조건 변수와 같은 동기화 도구를 활용하여 스레드 간 데이터를 안전하게 공유하며, 데드락과 같은 문제를 방지하는 전략을 구현할 수 있습니다. 본 기사를 통해 멀티스레드 환경에서 데이터 일관성을 유지하는 핵심 기술을 습득할 수 있습니다.

목차