이 코드는 멀티스레딩을 활용한 은행 계좌 입금 처리를 설명하는 예제입니다.
여기서 pthread를 사용하여 두 개의 스레드가 동시에 입금을 수행하며, 뮤텍스(pthread_mutex_t)를 사용하여 경쟁 상태(Race Condition)를 방지합니다.
🔹 1. 코드 개요
- 초기 잔액 조회 (read_balance())
- 두 개의 스레드 생성 (pthread_create()):
- 하나는 300원 입금
- 하나는 200원 입금
- 두 스레드가 종료할 때까지 대기 (pthread_join())
- 뮤텍스 제거 (pthread_mutex_destroy())
- 최종 잔액 출력
📌 목표:
- 올바르게 동기화하면 입금 후 총 잔액은 500원 증가해야 함.
- 뮤텍스를 사용하지 않으면 경쟁 상태(Race Condition)로 인해 값이 엉망이 될 수 있음.
🔹 2. 코드 상세 분석
pthread_mutex_t mutex; // 뮤텍스 선언 (전역 변수)
- mutex는 스레드 간 공유 자원(잔액)에 대한 동시 접근을 방지하는 역할.
✅ (1) 초기 잔액 확인
int before = read_balance();
printf("Before: %d\n", before);
- read_balance()는 현재 은행 잔액을 반환하는 함수.
- 입금 전 잔액을 출력.
✅ (2) 뮤텍스 초기화
pthread_mutex_init(&mutex, NULL);
- pthread_mutex_init()을 호출하여 mutex를 초기화.
- 뮤텍스를 사용하기 전에 반드시 초기화해야 함.
✅ (3) 두 개의 스레드 생성
int deposit1 = 300;
int deposit2 = 200;
pthread_t thread1;
pthread_t thread2;
pthread_create(&thread1, NULL, deposit, (void*) &deposit1);
pthread_create(&thread2, NULL, deposit, (void*) &deposit2);
- 두 개의 스레드(thread1, thread2)를 생성하여 각각 300원과 200원을 입금.
- deposit 함수가 실행되며, 인자로 입금할 금액을 전달 (void* 형 변환 사용).
✅ (4) 두 스레드가 종료할 때까지 대기
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
- pthread_join()은 해당 스레드가 끝날 때까지 기다리는 함수.
- 두 개의 입금 스레드가 모두 실행을 마칠 때까지 기다림.
✅ (5) 뮤텍스 제거
pthread_mutex_destroy(&mutex);
- 뮤텍스를 제거하여 리소스를 해제.
- 사용이 끝난 뮤텍스를 pthread_mutex_destroy()로 해제해야 메모리 누수 방지.
✅ (6) 최종 잔액 출력
int after = read_balance();
printf("After: %d\n", after);
- 입금 후 최종 잔액을 출력.
🔹 3. deposit 함수 구현
스레드가 실행할 deposit 함수는 아래와 같이 구현될 수 있음:
void* deposit(void* arg)
{
int amount = *(int*)arg; // 전달된 입금 금액을 정수로 변환
pthread_mutex_lock(&mutex); // 🔒 뮤텍스 잠금 (임계 영역 보호)
int balance = read_balance(); // 현재 잔액 조회
balance += amount; // 잔액 증가
write_balance(balance); // 새로운 잔액 저장
pthread_mutex_unlock(&mutex); // 🔓 뮤텍스 해제
return NULL;
}
🔹 4. 뮤텍스 없이 실행하면? (경쟁 상태 발생)
❌ 문제 발생 가능성
뮤텍스를 사용하지 않으면 다음과 같은 상황이 발생할 수 있음:
시간 스레드 1 (300원 입금) 스레드 2 (200원 입금)
T1 | 잔액 읽음: 1000 | - |
T2 | - | 잔액 읽음: 1000 |
T3 | 1000 + 300 = 1300 저장 | - |
T4 | - | 1000 + 200 = 1200 저장 |
T5 | 최종 잔액: 1200 | - |
💥 잔액이 1500이 아닌 1200으로 잘못 계산됨!
→ 두 스레드가 동시에 읽고 변경하여 덮어씌워지는 Race Condition 발생!
🔹 5. 뮤텍스를 사용하면?
시간 스레드 1 (300원 입금) 스레드 2 (200원 입금)
T1 | 🔒 mutex 잠금 | - |
T2 | 잔액 읽음: 1000 | - |
T3 | 1000 + 300 = 1300 저장 | - |
T4 | 🔓 mutex 해제 | - |
T5 | - | 🔒 mutex 잠금 |
T6 | 잔액 읽음: 1300 | - |
T7 | 1300 + 200 = 1500 저장 | - |
T8 | 🔓 mutex 해제 | - |
✅ 정확한 값(1500) 저장 가능! 🚀
뮤텍스가 lock/unlock을 통해 한 번에 하나의 스레드만 잔액을 변경할 수 있도록 보호하기 때문.
🔹 6. 결론
✅ 멀티스레딩 환경에서는 동기화(mutex)가 필수!
✅ pthread_mutex_lock()과 pthread_mutex_unlock()을 활용해 Race Condition 방지!
✅ 동시에 실행되는 두 스레드가 동일한 자원(잔액)을 수정하면 데이터 충돌이 발생할 수 있음
✅ 뮤텍스를 사용하면 두 스레드가 순차적으로 잔액을 업데이트하여 올바른 결과를 보장! 🚀
이제 입금 후 올바른 최종 잔액이 보장되는 코드를 만들 수 있습니다! 🎯
1. pthread_create(&thread1, NULL, deposit, (void*) &deposit1);
🔹 개념
- pthread_create()는 새로운 스레드를 생성하여 특정 함수를 실행하는 역할을 함.
- 이 함수가 실행되면 deposit 함수가 새로운 스레드에서 실행됨.
- deposit1 변수(값: 300)를 deposit 함수의 인자로 전달.
✅ 사용된 코드 분석
pthread_create(&thread1, NULL, deposit, (void*) &deposit1);
📌 매개변수 설명
매개변수 설명
&thread1 | 생성된 스레드의 ID가 저장될 변수 (pthread_t 타입) |
NULL | 스레드의 속성을 기본값으로 사용 (pthread_attr_t 타입의 옵션을 줄 수도 있음) |
deposit | 실행할 함수 (void* deposit(void* arg)) |
(void*) &deposit1 | deposit 함수의 인자로 전달할 값 (int deposit1 = 300; 의 주소) |
✅ 예제 코드
#include <stdio.h>
#include <pthread.h>
void* deposit(void* arg) {
int amount = *(int*)arg; // void*를 int*로 변환하여 값 가져오기
printf("Depositing %d\n", amount);
return NULL;
}
int main() {
pthread_t thread1;
int deposit1 = 300;
// 스레드 생성
pthread_create(&thread1, NULL, deposit, (void*)&deposit1);
// 스레드 종료까지 대기
pthread_join(thread1, NULL);
return 0;
}
🔍 실행 결과
Depositing 300
✔ deposit1 변수의 주소를 전달했기 때문에 deposit 함수에서 해당 값을 사용할 수 있음.
✔ deposit 함수가 실행되면서 "Depositing 300"이 출력됨.
2. pthread_join(thread1, NULL);
🔹 개념
- pthread_join()은 특정 스레드가 종료될 때까지 기다리는 함수.
- 만약 pthread_join()을 호출하지 않으면, 메인 스레드(main thread)가 종료되면서 다른 스레드도 함께 종료될 수 있음.
✅ 사용된 코드 분석
pthread_join(thread1, NULL);
📌 매개변수 설명
매개변수 설명
thread1 | 기다릴 스레드의 ID (pthread_create()에서 생성한 스레드) |
NULL | 스레드의 반환 값을 저장할 포인터 (필요하면 void** 타입으로 지정 가능) |
✅ 예제 코드 (스레드 종료 확인)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> // sleep() 함수 사용을 위해 추가
void* print_message(void* arg) {
printf("Hello from thread!\n");
sleep(2); // 2초 동안 대기
printf("Thread finishing...\n");
return NULL;
}
int main() {
pthread_t thread;
printf("Main thread: Creating a new thread...\n");
pthread_create(&thread, NULL, print_message, NULL);
printf("Main thread: Waiting for thread to finish...\n");
pthread_join(thread, NULL);
printf("Main thread: All threads finished, exiting program.\n");
return 0;
}
🔍 실행 결과
Main thread: Creating a new thread...
Main thread: Waiting for thread to finish...
Hello from thread!
Thread finishing...
Main thread: All threads finished, exiting program.
✔ pthread_create()가 실행된 후 새로운 스레드에서 print_message 함수 실행
✔ pthread_join()이 실행되지 않았다면, 메인 스레드가 먼저 종료될 수 있음
✔ pthread_join() 덕분에 새로운 스레드가 끝날 때까지 기다림
3. pthread_create() 없이 pthread_join()을 호출하면?
❌ 잘못된 코드
pthread_join(thread1, NULL); // thread1이 존재하지 않으면 실행할 수 없음
- pthread_create()로 thread1을 만들지 않았다면, pthread_join(thread1, NULL);을 호출하면 오류 발생.
4. pthread_create()와 pthread_join()의 실행 순서
실행 순서 설명
pthread_create(&thread1, NULL, deposit, (void*)&deposit1); | 새로운 스레드 시작 (deposit 실행) |
pthread_join(thread1, NULL); | thread1이 종료될 때까지 기다림 |
main() 종료 | 모든 스레드가 종료된 후 프로그램 종료 |
5. 결론
✅ pthread_create()는 새로운 스레드를 만들고, 특정 함수를 실행하도록 함.
✅ pthread_join()은 해당 스레드가 끝날 때까지 메인 스레드를 멈추고 기다림.
✅ 멀티스레딩 환경에서 pthread_join()을 적절히 사용해야 프로그램이 비정상적으로 종료되지 않음! 🚀
'C Language' 카테고리의 다른 글
철학자의 포크 할당 로직 설명 (0) | 2025.03.14 |
---|---|
공유 뮤텍스를 사용하는 이유와 예시 (0) | 2025.03.14 |
eat 함수 (0) | 2025.03.13 |
pthread_mutex_init NULL 초기화 (0) | 2025.03.13 |
뮤텍스 포인터 차이 (0) | 2025.03.13 |