본문 바로가기

프로그래밍/CS

[Study] 뮤텍스 (Mutex)

뮤텍스(Mutex)란?
: 뮤텍스(Mutex)는 Mutual Exclusion의 줄임말로, 다중 스레드 환경에서 공유 자원에 대한 동시 접근을 방지하기 위한
동기화 매커니즘이다. 뮤텍스를 사용하면 여러 스레드가 동시에 공유 자원에 접근하지 못하게 해
데이터 경합이나 데이터 불일치 문제를 방지할 수 있다.


뮤텍스의 필요성
: 프로그램에서 여러 스레드가 동시에 실행되면서, 공유 자원을 동시 접근하는 상황이 발생할 수 있다.
예를 들어, 여러 스레드가 동시에 하나의 변수에 접근하여 값을 변경하려고 할 때,
한 스레드가 작업을 완료하기 전에 다른 스레드가 그 변수에 접근하면 정확하지 않은 값이 나올 수 있다.
 
이를 방지하기 위해, 특정 스레드가 자원에 접근할 때 다른 스레드는 그 자원이 잠겨 있다는 것을 알 수 있도록 하는 장치가 필요한데, 뮤텍스는 이 역할을 수행하는 잠금장치로 이용된다. (따라서 Mutex를 Lock 이라고도 한다.)


뮤텍스에 대해서 비유적으로 설명한 글을 인용하자면, 화장실이 하나 뿐인 식당과 같다.

 
1. 화장실이라는 공유자원을 이용하기 위해 한 사람이 접근하고

2. 그 사람이 쓰는 동안은 공유자원이 문(MUTEX)에 의해 LOCK되어 이용하지 못한다.

3. 공유자원을 사용하고 난 후, 그 다음 대기열에 있던 사람이 이용하기 위해 접근한다.


뮤텍스의 동작원리
 
1. 잠금 (Lock)
: 위에서 언급 했던 것처럼 어떤 스레드가 공유 자원에 접근하려고 하면, 뮤텍스를 잠궈 그 자원을 사용하는 동안
다른 스레드가 접근하지 못하게 만든다. 만약 접근을 시도하면, 해당 스레드는 잠금이 해제될 때까지 대기하게 된다.
2. 해제 (Unlock)
: 작업이 끝나고 나면, 스레드는 뮤텍스를 해제하며, 대기 중인 스레드가 자원에 접근할 수 있게 된다.
 
이 잠금과 해제 과정을 통해서 다중 스레드가 순차적으로 공유 자원에 접근하여 어떠한 데이터의 오류 없이
제어할 수 있도록 해주는 것이 뮤텍스의 동작이다.


뮤텍스 함수
 
1. pthread_mutex_init()

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

: 뮤텍스를 초기화하는 함수.
pthread_mutex_t 로 선언된 mutex 객체의 포인터를 인자로 받고,
pthread_mutexattr_t 로 지정된 속성으로 뮤텍스를 초기화한다. (NULL 일 경우 기본 속성)
성공시 0 반환, 실패 시 에러코드 errno 를 반환한다.
 
2. pthread_mutex_lock()

int pthread_mutex_lock(pthread_mutex_t *mutex);

: 뮤텍스를 잠그는 함수.
만약 뮤텍스가 이미 잠긴 상태라면, 잠금이 풀릴 때까지 해당 스레드는 대기한다.
pthread_mutex_t 로 선언된 mutex 객체의 포인터를 인자로 받는다.
성공시 0 반환, 실패 시 에러코드 errno 를 반환한다.
 
3. pthread_mutex_unlock()

int pthread_mutex_unlock(pthread_mutex_t *mutex);

: 뮤텍스를 해제하는 함수.
lock으로 걸어둔 뮤텍스의 잠금을 해제하면, 다른 대기 중인 스레드가 뮤텍스를 잠글 수 있게 된다.
pthread_mutex_t 로 선언된 mutex 객체의 포인터를 인자로 받는다.
성공시 0 반환, 실패 시 에러코드 errno 를 반환한다.
 
4. pthread_mutex_destroy()

int pthread_mutex_destroy(pthread_mutex_t *mutex);

: 뮤텍스를 해제하여 더 이상 사용하지 않도록 하는 함수.
뮤텍스를 더 이상 사용하지 않을 때 호출하고, 뮤텍스 리소스를 해제하고 반환한다. (메모리 free X)
pthread_mutex_t 로 선언된 mutex 객체의 포인터를 인자로 받는다.
성공시 0 반환, 실패 시 에러코드 errno 를 반환한다.


사용 예시 -  1) mutex를 사용하지 않은 경우

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

int shared_resource = 0;
pthread_mutex_t mutex;

void* thread_function_A(void* arg) {
    // pthread_mutex_lock(&mutex);
    for (int i = 0; i < 10; i++) {
        shared_resource++;
        printf("thread_A: %d\n", shared_resource);
        sleep(1);
    }
    // pthread_mutex_unlock(&mutex);
    return NULL;
}

void* thread_function_B(void* arg) {
    // pthread_mutex_lock(&mutex);
        for (int i = 0; i < 10; i++) {
        shared_resource++;
        printf("thread_B: %d\n", shared_resource);
        sleep(1);
    }
    // pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t threads[2];

    // pthread_mutex_init(&mutex, NULL);
    pthread_create(&threads[0], NULL, thread_function_A, NULL);
    sleep(1);
    pthread_create(&threads[1], NULL, thread_function_B, NULL);
    
    pthread_join(threads[0], NULL);
    pthread_join(threads[1], NULL);

    // pthread_mutex_destroy(&mutex);
    printf("shared_resource = %d\n", shared_resource);
    
    return 0;
}

 
출력 - std_output)

 
mutex를 주석처리하여 실행해본 결과이다.
 
두 스레드를 각각 다른 함수를 동작하도록 해줬는데, 공유 자원인 shared resource를 늘려가는 동작은 동일하다.
하지만 스레드 두 개가 같은 공유 자원을 거의 동시에 추가해주며 교차적으로 실행되는 모습을 볼 수 있다.
이는 스레드의 공유자원 사용이 충돌 될 수 있고 공유 자원 값에 문제가 발생할 여지가 있는 모습이다.


사용 예시 -  2) mutex를 사용한 경우

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

int shared_resource = 0;
pthread_mutex_t mutex;

void* thread_function_A(void* arg) {
    pthread_mutex_lock(&mutex);
    for (int i = 0; i < 10; i++) {
        shared_resource++;
        printf("thread_A: %d\n", shared_resource);
        sleep(1);
    }
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* thread_function_B(void* arg) {
    pthread_mutex_lock(&mutex);
        for (int i = 0; i < 10; i++) {
        shared_resource++;
        printf("thread_B: %d\n", shared_resource);
        sleep(1);
    }
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t threads[2];

    pthread_mutex_init(&mutex, NULL);
    pthread_create(&threads[0], NULL, thread_function_A, NULL);
    sleep(1);
    pthread_create(&threads[1], NULL, thread_function_B, NULL);
    
    pthread_join(threads[0], NULL);
    pthread_join(threads[1], NULL);

    pthread_mutex_destroy(&mutex);
    printf("shared_resource = %d\n", shared_resource);
    
    return 0;
}

 
출력 - std_output)

 
하지만 뮤텍스를 사용한 상태에서는 thread_A가 먼저 공유자원을 선점해 lock하고 공유자원을 늘려주고,
unlock 함과 동시에 thread_B가 lock하며 공유자원을 가져와 사용한다.
이러한 과정이 공유자원을 사용하는 데에 있어 뮤텍스를 활용하여 문제발생을 막아주는 것이다.

'프로그래밍 > CS' 카테고리의 다른 글

[Study] 스레드 (Thread)  (2) 2024.08.31
[study] 프로세스 (Process)  (2) 2024.06.02
Recent Posts
Popular Posts
Recent Comments