Computer >> 컴퓨터 >  >> 프로그래밍 >> C 프로그래밍

스레드 동기화로 순서대로 숫자 출력하기 - C/C++ 뮤텍스와 조건 변수 활용법

여러 개의 스레드가 동시에 실행될 때 각 스레드가 정해진 순서대로 숫자를 출력하도록 만드는 방법을 알아보겠습니다. 이 기법은 멀티스레드 프로그래밍에서 스레드 동기화(Thread Synchronization)의 핵심 개념을 이해하는 데 큰 도움이 됩니다.

동작 원리

먼저 n개의 스레드를 생성한 후, 이들을 동기화합니다. 목표는 첫 번째 스레드가 1을 출력하고, 두 번째 스레드가 2를 출력하는 식으로 번갈아 가며 숫자를 찍는 것입니다.

핵심 아이디어는 다음과 같습니다.

  • 스레드가 출력을 시도할 때 먼저 뮤텍스(Mutex)로 공유 자원을 잠급니다(lock).
  • 자신의 차례가 아니라면 조건 변수(Condition Variable)를 통해 대기 상태로 전환됩니다.
  • 출력이 끝나면 다음 스레드에게 신호(signal)를 보내고 잠금을 해제(unlock)합니다.

이렇게 하면 한 번에 하나의 스레드만 임계 영역(critical section)에 접근할 수 있으므로, 숫자가 항상 올바른 순서로 출력됩니다.

예제 코드

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t* cond = NULL;
int threads;
volatile int count = 0;
void* sync_thread(void* num) { // 스레드 동기화를 위한 함수
    int thread_number = *(int*)num;
    while (1) {
        pthread_mutex_lock(&mutex); // 해당 구역 잠금
        if (thread_number != count) { // 스레드 번호가 count와 다르면
            // 하나를 제외한 나머지 스레드를 대기 상태로 전환
            pthread_cond_wait(&cond[thread_number], &mutex);
        }
        printf("%d ", thread_number + 1); // 스레드 번호 출력
        count = (count+1)%(threads);
        // 다음 스레드에 알림 전송
        pthread_cond_signal(&cond[count]);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main() {
    pthread_t* thread_id;
    volatile int i;
    int* thread_arr;
    printf("\nEnter number of threads: ");
    scanf("%d", &threads);
    // 조건 변수, 스레드 ID, 배열에 메모리 할당
    cond = (pthread_cond_t*)malloc(sizeof(pthread_cond_t) * threads);
    thread_id = (pthread_t*)malloc(sizeof(pthread_t) * threads);
    thread_arr = (int*)malloc(sizeof(int) * threads);
    for (i = 0; i < threads; i++) { // 스레드 생성
        thread_arr[i] = i;
        pthread_create(&thread_id[i], NULL, sync_thread, (void*)&thread_arr[i]);
    }
    // 스레드 종료 대기
    for (i = 0; i < threads; i++) {
        pthread_join(thread_id[i], NULL);
    }
    return 0;
}

코드 설명

  • pthread_mutex_t mutex: 여러 스레드가 동시에 공유 변수에 접근하지 못하도록 보호하는 뮤텍스입니다.
  • pthread_cond_t* cond: 각 스레드마다 별도의 조건 변수 배열을 두어, 특정 스레드만 개별적으로 깨울 수 있게 합니다.
  • pthread_cond_wait(): 자신의 차례가 아닌 스레드를 대기 상태로 만들고, 뮤텍스를 자동으로 해제하여 다른 스레드가 진입할 수 있도록 합니다.
  • pthread_cond_signal(): 현재 출력을 마친 스레드가 다음 순번의 스레드를 깨우는 역할을 합니다.
  • count = (count+1)%(threads): 카운터를 순환시켜 마지막 스레드 출력 후 다시 첫 번째 스레드로 돌아가도록 합니다.

실행 결과

$ g++ test.cpp -lpthread
$ ./a.out

Enter number of threads: 5
1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3
4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5
1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3
4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1
2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4
5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2
3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5
...
...
...

실행 결과를 보면 5개의 스레드가 경쟁 상태(race condition) 없이 항상 1 2 3 4 5 순서를 유지하며 무한히 반복 출력하는 것을 확인할 수 있습니다. 컴파일 시에는 POSIX 스레드 라이브러리를 링크하기 위해 반드시 -lpthread 옵션을 추가해야 합니다.