본문 바로가기
CS 지식/시스템 프로그래밍

[C언어] pthread 라이브러리를 이용한 스레드 동기화

by 코딩하는 동현 2025. 1. 28.

Threads Synchornization

 

 

Multi Thread에서 전역 변수, 힙 영역, malloc 등은 Data Sharing을 한다.

다음 코드는 입력한 초만큼 전역 변수 cnt가 증가하는 예제이다.

volatile: 컴파일러 최적화 방지. cnt 변수를 레지스터가 아닌 메모리에서 직접 읽고 쓰게 만듦

volatile int cnt;

void* do_loop (void *loop)
{
    while(1)
    cnt++;
    return NULL;
}

int main(int argc, char *argv[]){
    int status, sec;
    pthread_t thread_id;
    cnt = 0;
    sec = atoi(argv[1]);
    if (pthread_create(&thread_id, NULL, do_loop, NULL) !=0){
        perror("pthread create");
        exit(1);
    }
    sleep(sec);
    pthread_cancel(thread_id);
    printf("counter = %d\n", cnt);
}

그 이유로 Race Condition이 발생할 수 있다.

아래는 Race Condition의 예제이다.

volatile int cnt;

void* do_loop1 (void *data)
{
    int* loop_p = (int *)data;
    for (int i = 0; i < *loop_p; i++)
    cnt++;
    
    return NULL;
}

void* do_loop2 (void *data)
{
    int* loop_p = (int *)data;
    for (int i = 0; i < *loop_p; i++)
    cnt--;
}

int main(int argc, char *argv[]){
    pthread_t thread_id[2];
    int loop_n = atoi(argv[1]);
    if (pthread_create(&thread_id[0], NULL, do_loop1, &loop_n) !=0){
        perror("pthread1 create");
        exit(1);
    }
    if (pthread_create(&thread_id[1], NULL, do_loop2, &loop_n) !=0){
        perror("pthread2 create");
        exit(1);
    }

    pthread_join(thread_id[0],NULL);
    pthread_join(thread_id[1],NULL);
    printf("counter = %d\n", cnt);
}

결과

  • 각 스레드의 반복 횟수가 많아질수록 경쟁 상태가 발생할 확률이 높아진다.
  • Race Condition: 실행할 때마다 결과 값이 달라지는 현상.


Locks: Basic Idea

Critical Section이 단일 Atomic Instruction처럼 동작하도록 Mutual Exclusion을 보장한다.
즉, 다른 스레드가 접근하지 못하게 막아준다.

lock_t mutex; // available, acquired 둘중 하나의 상태를 저장
	
lock(&mutex);
balance = balance + 1;
unlock(&mutex);

lock()

  • Lock을 얻으려 시도하며, 다른 스레드가 hold 중이 아니라면 lock을 획득한다.
  • Lock을 건 스레드만 Critical Section에 접근 가능하다.

Mutex

pthread 라이브러리의 자료구조로, Thread 동기화를 구현한다.

Static Initialization

 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALZER; // 정적 초기화​

Dynamic Initialization

pthread_mutex_t mutex; // 이걸 인자로 &mutex 넣으면 된다.

int pthread_mutex_init(
	pthread_mutex_t *mutex, // mutex 변수의 포인터
	pthread_mutexattr_t *attr) // NULL, mutex 속성
) // 반환 0 또는 에러코드

Locking Mutex

  • pthread_mutex_lock()
    • 성공 시 lock을 획득, 실패 시 대기.
    • 선발대만 lock을 얻고, 나머지는 sleep 상태가 된다.
  • pthread_mutex_trylock()
    • 성공 시 lock을 획득, 실패 시 바로 반환.
    • 후발대는 대기하지 않고 즉시 리턴.
int pthread_mutex_lock(pthread_mutex_t *mutex) // 성공하면 잡음. 성공 못하면 기다림


int pthread_mutex_trylock(pthread_mutex_t *mutex) // 성공하면 잡음. 성공 못하면 바로 리턴(알려줌)

Unlocking Mutex

  • pthread_mutex_unlock()
    • Mutex를 release.
    • Block 상태의 스레드가 있으면 Scheduling Policy에 따라 mutex를 얻을 스레드 결정.
int pthread_mutex_unlock(pthread_mutex_t *mutex)

Race Condition 방지: Mutex 사용 예제

  • pthread_mutex_destroy(): 프로그램 종료 시 mutex 자원 해제.
volatile int cnt;
// int cnt; // volatile을 안한다면???
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* do_loop1 (void *data)
{
    int* loop_p = (int *)data;
    pthread_mutex_lock(&mutex);
    for (int i = 0; i < *loop_p; i++)
    cnt++;
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* do_loop2 (void *data)
{
    int* loop_p = (int *)data;
    pthread_mutex_lock(&mutex);
    for (int i = 0; i < *loop_p; i++)
    cnt--;
    pthread_mutex_unlock(&mutex);
}

int main(int argc, char *argv[]){
    pthread_t thread_id[2];
    int loop_n = atoi(argv[1]);
    if (pthread_create(&thread_id[0], NULL, do_loop1, &loop_n) !=0){
        perror("pthread1 create");
        exit(1);
    }
    if (pthread_create(&thread_id[1], NULL, do_loop2, &loop_n) !=0){
        perror("pthread2 create");
        exit(1);
    }

    pthread_join(thread_id[0],NULL);
    pthread_join(thread_id[1],NULL);
    printf("counter = %d\n", cnt);
    pthread_mutex_destroy(&mutex);
}

 

반응형

댓글