여러 개의 스레드가 동시에 실행될 때 각 스레드가 정해진 순서대로 숫자를 출력하도록 만드는 방법을 알아보겠습니다. 이 기법은 멀티스레드 프로그래밍에서 스레드 동기화(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 옵션을 추가해야 합니다.