본문 바로가기
프로그래밍/C

POSIX thread, mutex 사용법

by eteo 2024. 6. 26.

 

 

Pthread는 모든 유닉스 계열 POSIX 시스템에서, 일반적으로 이용되는 라이브러리로 병렬적으로 작동하는 소프트웨어 작성을 위해 사용할 수 있다. 소스코드에선 #include <pthread.h>로 헤더를 포함하고 컴파일 시 -pthread 옵션을 붙여 컴파일 한다.

 

 

 

주요 thread 함수

 

 

pthread_create

새로운 스레드를 생성하고 지정된 함수 start_routine을 실행한다.

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • pthread_t *thread: 생성된 스레드의 식별자를 저장할 포인터, 해당 식별자를 통해 이후 스레드를 제어할 수 있다.
  • const pthread_attr_t *attr: 스레드의 속성을 지정하는 포인터로 기본 속성을 사용하려면 NULL을 전달한다.
  • void *(*start_routine) (void *): 스레드가 실행할 함수에 대한 포인터로 스레드가 실행할 함수는 해당 형식을 지켜야 한다.
  • void *arg: 스레드 함수에 전달할 인자이다.
  • 리턴값 : 성공 시 0을 반환하고, 실패 시 오류 코드를 반환한다.

 

 

pthread_join

지정된 스레드가 종료될 때까지 대기한다.

int pthread_join(pthread_t thread, void **retval);
  • pthread_t thread: 대기할 스레드의 식별자
  • void **retval: 스레드가 종료 시 반환하는 값을 저장할 포인터이다.
  • 리턴값 : 성공 시 0을 반환하고, 실패 시 오류 코드를 반환한다.

 

pthread_detach

지정된 스레드를 분리(detach) 상태로 설정한다. 그렇게 분리된 스레드는 종료 될 때 그 자원을 자동으로 해제하며, 부모스레드에서는 pthread_join으로 스레드 종료를 기다리지 않고 detach한 이후 다른 작업을 수행할 수 있다.

 

int pthread_detach(pthread_t thread);
  • pthread_t thread: 분리할 스레드의 식별자
  • 리턴값 : 성공 시 0을 반환하고, 실패 시 오류 코드를 반환한다.

 

 


 

 

주요 mutex 함수

 

 

pthread_mutex_init

뮤텍스 객체를 초기화 하는 함수로 뮤텍스가 사용되기 전에 반드시 호출되어야 한다.

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • pthread_mutex_t *mutex: 초기화할 뮤텍스 객체에 대한 포인터이다.
  • const pthread_mutexattr_t *attr: 뮤텍스의 속성을 지정하는 포인터로 기본 속성을 사용하려면 NULL을 전달한다.
  • 리턴값 : 성공 시 0을 반환하고, 실패 시 오류 코드를 반환한다.

 

 

pthread_mutex_destroy

뮤텍스 객체를 해제한다. 이 함수는 더이상 뮤텍스가 사용되지 않을 때 호출한다.

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • pthread_mutex_t *mutex: 해제할 뮤텍스 객체에 대한 포인터
  • 리턴값 : 성공 시 0을 반환하고, 실패 시 오류 코드를 반환한다.

 

 

pthread_mutex_lock

뮤텍스를 잠근다. 만약 뮤텍스가 이미 잠겨있다면 뮤텍스가 해제될 때까지 대기한다.

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • pthread_mutex_t *mutex: 잠글 뮤텍스 객체에 대한 포인터
  • 리턴값 : 성공 시 0을 반환하고, 실패 시 오류 코드를 반환한다.

 

 

 

 

pthread_mutex_unlock

잠긴 뮤텍스를 해제한다. 이 함수는 반드시 pthread_mutex_lock로 뮤텍스를 잠근 스레드에서 쌍으로 호출되어야 한다. pthread_mutex_lock와 pthread_mutex_unlock 사이가 임계구역이 된다.

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • pthread_mutex_t *mutex: 해제할 뮤텍스 객체에 대한 포인터
  • 리턴값 : 성공 시 0을 반환하고, 실패 시 오류 코드를 반환한다.

 

 

 

 

사용 예시

 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// foo 구조체 정의
struct foo {
        int             f_count;
        pthread_mutex_t f_lock;
        /* ... more stuff here ... */
};

// foo 메모리 할당 함수
struct foo *foo_alloc(void) /* allocate the object */
{
        struct foo *fp = NULL;

        if((fp = malloc(sizeof(struct foo))) != NULL) {
                fp->f_count = 0;
                if(pthread_mutex_init(&fp->f_lock, NULL) != 0) {
                        free(fp);
                        return(NULL);
                }
        }
        return(fp);
}

// foo 메모리 해제 함수
void foo_free(struct foo *fp)
{
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
}

// 스레드 함수
void *thread_func(void *arg)
{
        struct foo *fp = (struct foo *)arg;

        for(int i = 0; i < 1000000; i++) {
                pthread_mutex_lock(&fp->f_lock);
                // 임계 구역 시작
                fp->f_count++;
                // 임계 구역 끝
                pthread_mutex_unlock(&fp->f_lock);
        }

        return ((void *)0);
}

int main(void)
{
        pthread_t tid1, tid2;
        struct foo *fp;

        fp = foo_alloc();
        if(fp == NULL) {
                fprintf(stderr, "foo_alloc failed\n");
                exit(1);
        }
		// 스레드1 생성
        if(pthread_create(&tid1, NULL, thread_func, (void *)fp) != 0) {
                fprintf(stderr, "Error creating thread 1\n");
                exit(1);
        }
		// 스레드2 생성
        if(pthread_create(&tid2, NULL, thread_func, (void *)fp) != 0) {
                fprintf(stderr, "Error creating thread 2\n");
                exit(1);
        }

        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);

        printf("Final count: %d\n", fp->f_count);

        foo_free(fp);

        return 0;
}

 

 

위 예시에서 pthread_mutex_lock과 pthread_mutex_unlock을 사용하여 임계 구역을 보호하면 f_count의 최종 값이 반복 횟수에 정확히 2배가 나오는데 pthread_mutex_lock과 pthread_mutex_unlock 부분을 주석처리하면 여러 스레드가 동시에 동일한 변수 f_count에 접근하고 수정하려고 시도하므로 데이터 일관성이 깨질 수가 있다.

 

 

 

 

코드조각 참조 : APUE