본문 바로가기

C Language

mutex 입금문제

이 코드는 멀티스레딩을 활용한 은행 계좌 입금 처리를 설명하는 예제입니다.
여기서 pthread를 사용하여 두 개의 스레드가 동시에 입금을 수행하며, 뮤텍스(pthread_mutex_t)를 사용하여 경쟁 상태(Race Condition)를 방지합니다.


🔹 1. 코드 개요

  1. 초기 잔액 조회 (read_balance())
  2. 두 개의 스레드 생성 (pthread_create()):
    • 하나는 300원 입금
    • 하나는 200원 입금
  3. 두 스레드가 종료할 때까지 대기 (pthread_join())
  4. 뮤텍스 제거 (pthread_mutex_destroy())
  5. 최종 잔액 출력

📌 목표:

  • 올바르게 동기화하면 입금 후 총 잔액은 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