뮤텍스(mutex) 사용

뮤텍스는 상호 배타적 잠금입니다. 단 하나의 스레드만이 잠글 수 있습니다.

뮤텍스는 동시 액세스로부터 데이터 또는 기타 자원을 보호하는 데 사용됩니다. 뮤텍스에는 뮤텍스의 특성을 지정하는 속성이 있습니다.

뮤텍스 속성 오브젝트

스레드와 마찬가지로 뮤텍스는 속성 오브젝트의 도움으로 작성됩니다. 뮤텍스 속성 오브젝트는 POSIX 옵션의 구현에 따라 여러 속성을 포함하는 추상적인 오브젝트입니다. pthread_mutexattr_t 유형의 변수를 통해 액세스합니다. AIX®에서 pthread_mutexattr_t 데이터 유형은 포인터입니다. 다른 시스템에서는 구조 또는 다른 데이터 유형일 수 있습니다.

뮤텍스 속성 오브젝트의 작성 및 영구 삭제

뮤텍스 속성 오브젝트는 pthread_mutexattr_init 서브루틴에 의해 기본값으로 초기화됩니다. 속성은 서브루틴에서 처리합니다. 스레드 속성 오브젝트는 pthread_mutexattr_destroy 서브루틴에 의해 삭제됩니다. 이 서브루틴은 스레드 라이브러리의 구현에 따라 pthread_mutexattr_init 서브루틴에서 동적으로 할당된 스토리지를 해제할 수 있습니다.

다음 예제에서는 뮤텍스 속성 오브젝트가 디폴트 값으로 작성 및 초기화되어 사용된 다음 마지막으로 제거됩니다.
pthread_mutexattr_t attributes;
                /* the attributes object is created */
...
if (!pthread_mutexattr_init(&attributes)) {
                /* the attributes object is initialized */
        ...
                /* using the attributes object */
        ...
        pthread_mutexattr_destroy(&attributes);
                /* the attributes object is destroyed */
}

동일한 속성 오브젝트를 사용하여 여러 뮤텍스를 작성할 수 있습니다. 또한 뮤텍스 작성 도중에 이 오브젝트를 수정할 수 있습니다. 뮤텍스가 작성될 때 속성 오브젝트는 작성되는 뮤텍스에 영향을 주지 않고 삭제될 수 있습니다.

뮤텍스 속성

다음과 같은 뮤텍스 속성이 정의됩니다.

속성 설명
프로토콜 뮤텍스의 우선순위 반전을 방지하는 데 사용되는 프로토콜을 지정합니다. 이 속성은 우선순위 상속 또는 우선순위 보호 POSIX 옵션에 따라 다릅니다.
Process-shared 뮤텍스의 프로세스 공유를 지정합니다. 이 속성은 POSIX 옵션을 공유하는 프로세스에 따라 다릅니다.

이러한 속성에 대한 자세한 정보는 스레드 라이브러리 옵션동기화 스케줄링을 참조하십시오.

뮤텍스 작성 및 영구 삭제

뮤텍스는 pthread_mutex_init 서브루틴을 호출하여 작성됩니다. 사용자가 뮤텍스 속성 오브젝트를 지정할 수 있습니다. 널(null) 포인터를 지정할 경우 뮤텍스는 디폴트 속성을 가집니다. 따라서,
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
...
pthread_mutexattr_init(&attr);
pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);
코드 프래그먼트는 다음과 같습니다.
pthread_mutex_t mutex;
...
pthread_mutex_init(&mutex, NULL);

작성된 뮤텍스의 ID는 mutex 매개변수를 통해 호출 스레드로 리턴됩니다. 뮤텍스 ID는 부전도성 오브젝트이며, pthread_mutex_t 유형입니다. AIX에서 pthread_mutex_t 데이터 유형은 구조입니다. 다른 시스템에서는 포인터 또는 다른 데이터 유형일 수 있습니다.

뮤텍스는 한 번 작성되어야 합니다. 그러나 동일한 mutex 매개변수 (예: 동일한 코드를 동시에 실행하는 두 개의 스레드) 에서 pthread_mutex_init 서브루틴을 두 번 이상 호출하지 마십시오. 뮤텍스 작성의 고유성은 다음과 같은 방법으로 보장할 수 있습니다.

  • 이 뮤텍스를 사용할 다른 스레드를 작성하기 전에 pthread_mutex_init 서브루틴을 호출합니다(예를 들어, 초기 스레드에서).
  • 일회성 초기화 루틴 내에서 pthread_mutex_init 서브루틴을 호출합니다. 자세한 정보는 1-시간 초기화 를 참조하십시오.
  • PTHREAD_MUTEX_INITIALIZER 정적 초기화 매크로에 의해 초기화된 정적 뮤텍스를 사용하면, 뮤텍스에는 기본 속성이 있습니다.
뮤텍스가 더 이상 필요하지 않으면 pthread_mutex_destroy 서브루틴을 호출하여 이를 삭제하십시오. 이 서브루틴은 pthread_mutex_init 서브루틴에서 할당된 스토리지를 교정할 수 있습니다. 뮤텍스를 영구 삭제한 후 같은 pthread_mutex_t 변수를 다시 사용하여 다른 뮤텍스를 작성할 수 있습니다. 예를 들어, 다음 코드 프래그먼트는 실제로 사용되지는 않지만 유효합니다.
pthread_mutex_t mutex;
...
for (i = 0; i < 10; i++) {
 
        /* creates a mutex */
        pthread_mutex_init(&mutex, NULL);
 
        /* uses the mutex */
 
        /* destroys the mutex */
        pthread_mutex_destroy(&mutex);
}

스레드 사이에 공유될 수 있는 모든 시스템 자원과 마찬가지로, 스레드를 종료하기 전에 스레드의 스택에 할당된 뮤텍스를 영구 삭제해야 합니다. 스레드 라이브러리는 링크된 뮤텍스 리스트를 유지합니다. 따라서, 뮤텍스가 할당된 스택이 사용 가능하면 리스트가 손상됩니다.

뮤텍스 유형

뮤텍스의 유형은 뮤텍스가 작업을 시작할 때의 작동 방법을 판별합니다. 다음과 같은 뮤텍스 유형이 존재합니다.
PTHREAD_MUTEX_DEFAULT 또는 PTHREAD_MUTEX_NORMAL
동일한 pthread가 먼저 잠금을 해제하지 않고 pthread_mutex_lock 서브루틴을 사용하여 두 번째로 잠금을 시도할 경우 교착 상태가 됩니다. 이것이 디폴트 유형입니다.
PTHREAD_MUTEX_ERRORCHECK
동일한 스레드가 먼저 뮤텍스를 잠금 해제하지 않고 두 번 이상 동일한 뮤텍스의 잠금을 시도할 경우 0이 아닌 값을 리턴하여 교착 상태를 예방합니다.
PTHREAD_MUTEX_RECURSIVE
pthread_mutex_lock 서브루틴에서 0이 아닌 리턴 값을 가져오거나 교착 상태가 되지 않고도 같은 pthread가 pthread_mutex_lock을 사용하여 뮤텍스를 반복적으로 잠글 수 있습니다. 다른 pthread가 사용할 수 있도록 뮤텍스를 잠금 해제하려면 pthread_mutex_lock 서브루틴을 호출한 횟수만큼 같은 pthread가 pthread_mutex_unlock 서브루틴을 호출해야 합니다.

뮤텍스 속성을 처음 작성할 때 디폴트 유형은 PTHREAD_MUTEX_NORMAL입니다. 뮤텍스를 작성한 후 pthread_mutexattr_settype API 라이브러리 호출을 사용하여 호출을 변경할 수 있습니다.

다음은 순환적 뮤텍스 유형을 작성 및 사용하는 예제입니다.
pthread_mutexattr_t    attr;
pthread_mutex_t         mutex;

pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mutex, &attr);

struct {
        int a;
        int b;
        int c;
} A;

f()
{
        pthread_mutex_lock(&mutex);
        A.a++;
        g();
        A.c = 0;
        pthread_mutex_unlock(&mutex);
}

g()
{
        pthread_mutex_lock(&mutex);
        A.b += A.a;
        pthread_mutex_unlock(&mutex);
}

뮤텍스의 잠금 및 잠금 해제

뮤텍스는 단순 잠금으로, 두 가지 상태(잠금 및 잠금 해제)입니다. 뮤텍스는 작성될 때 잠금 해제됩니다. pthread_mutex_lock 서브루틴은 다음 조건에서 지정된 뮤텍스를 잠급니다.

  • 뮤텍스가 잠금 해제되면 서브루틴이 뮤텍스를 잠급니다.
  • 다른 스레드에서 뮤텍스를 잠근 경우 서브루틴은 뮤텍스가 잠금 해제될 때까지 호출 스레드를 차단합니다.
  • 호출 스레드에서 뮤텍스를 이미 잠근 경우 서브루틴은 뮤텍스의 유형에 따라 오류를 리턴하거나 계속 차단할 수 있습니다.

pthread_mutex_trylock 서브루틴은 다음 조건에서 호출 스레드를 차단하지 않고 pthread_mutex_lock 서브루틴처럼 작동합니다.

  • 뮤텍스가 잠금 해제되면 서브루틴이 뮤텍스를 잠급니다.
  • 스레드에서 뮤텍스를 이미 잠근 경우 서브루틴이 오류를 리턴합니다.

뮤텍스를 잠근 스레드를 뮤텍스의 소유자라고 합니다.

pthread_mutex_unlock 서브루틴은 다음 조건에서 호출 뮤텍스가 소유하는 경우 지정된 뮤텍스를 잠금 해제 상태로 재설정합니다.

  • 뮤텍스가 이미 잠금 해제된 경우 서브루틴은 오류를 리턴합니다.
  • 호출 스레드가 뮤텍스를 소유한 경우 서브루틴은 뮤텍스를 잠금 해제합니다.
  • 다른 스레드가 뮤텍스를 소유한 경우 서브루틴은 뮤텍스의 유형에 따라 오류를 리턴하거나 뮤텍스를 잠금 해제할 수 있습니다. 동일한 pthread가 보통 뮤텍스를 잠그고 잠금 해제하므로, 뮤텍스를 잠금 해제하는 것은 권장하지 않습니다.

잠금에서는 취소점을 제공하지 않으므로, 뮤텍스를 대기하는 동안 잠긴 스레드는 취소할 수 없습니다. 따라서, 동시 액세스로부터 데이터를 보호하는 경우와 같이 짧은 기간 동안만 뮤텍스를 사용하는 것이 좋습니다. 자세한 정보는 취소 지점스레드 취소를 참조하십시오.

뮤텍스를 사용하여 데이터 보호

뮤텍스는 다른 스레드 동기화 기능을 빌드할 수 있는 하위 레벨 기본요소 또는 데이터 보호 잠금으로 제공됩니다. 긴 잠금 및 출력기 구현에 대한 자세한 정보는 뮤텍스 사용을 참조하십시오.

뮤텍스 사용 예제

뮤텍스는 동시 액세스로부터 데이터를 보호하는 데 사용될 수 있습니다. 예를 들어, 데이터베이스 애플리케이션은 여러 스레드를 작성하여 여러 요청을 동시에 처리할 수 있습니다. 데이터베이스 자체는 db_mutex라는 뮤텍스를 사용하여 보호합니다. 예를 들면, 다음과 같습니다.
/* the initial thread */
pthread_mutex_t mutex;
int i;
...
pthread_mutex_init(&mutex, NULL);    /* creates the mutex      */
for (i = 0; i < num_req; i++)        /* loop to create threads */
        pthread_create(th + i, NULL, rtn, &mutex);
...                                  /* waits end of session   */
pthread_mutex_destroy(&mutex);       /* destroys the mutex     */
...

/* the request handling thread */
...                                  /* waits for a request  */
pthread_mutex_lock(&db_mutex);       /* locks the database   */
...                                  /* handles the request  */
pthread_mutex_unlock(&db_mutex);     /* unlocks the database */
...

초기 스레드는 뮤텍스와 모든 요청 처리 스레드를 작성합니다. 뮤텍스는 스레드의 시작점 루틴에 대한 매개변수를 사용하여 스레드로 전달됩니다. 실제 프로그램에서 뮤텍스의 주소는 작성된 스레드에 전달된 복잡한 데이터 구조의 필드일 수 있습니다.

교착 상태 피하기

멀티스레드 애플리케이션이 교착 상태가 될 수 있는 방법에는 여러 가지가 있습니다. 예를 들면 다음과 같습니다.
  • 디폴트 유형 PTHREAD_MUTEX_NORMAL로 작성된 뮤텍스는 교착 상태를 발생하지 않고는 같은 pthread에 의해 다시 잠길 수 없습니다.
  • 역순으로 뮤텍스를 잠그면 애플리케이션이 교착 상태가 될 수 있습니다. 예를 들어, 다음 코드 프래그먼트는 스레드 A와 B 사이에서 교착 상태를 만들 수 있습니다.
    /* Thread A */
    pthread_mutex_lock(&mutex1);
    pthread_mutex_lock(&mutex2);
    
    /* Thread B */
    pthread_mutex_lock(&mutex2);
    pthread_mutex_lock(&mutex1);
  • 애플리케이션은 자원 교착 상태에서 교착 상태가 됩니다. 예를 들면, 다음과 같습니다.
    struct {
                    pthread_mutex_t mutex;
                    char *buf;
            } A;
    
    struct {
                    pthread_mutex_t mutex;
                    char *buf;
            } B;
    
    struct {
                    pthread_mutex_t mutex;
                    char *buf;
            } C;
    
    use_all_buffers()
    {
            pthread_mutex_lock(&A.mutex);
            /* use buffer A */
    
            pthread_mutex_lock(&B.mutex);
            /* use buffers B */
    
            pthread_mutex_lock(&C.mutex);
            /* use buffer C */
    
            /* All done */
            pthread_mutex_unlock(&C.mutex);
            pthread_mutex_unlock(&B.mutex);
            pthread_mutex_unlock(&A.mutex);
    }
    
    use_buffer_a()
    {
            pthread_mutex_lock(&A.mutex);
            /* use buffer A */
            pthread_mutex_unlock(&A.mutex);
    }
    
    functionB()
    {
            pthread_mutex_lock(&B.mutex);
            /* use buffer B */
            if (..some condition)
            { 
              use_buffer_a();
            }
            pthread_mutex_unlock(&B.mutex);
    }
    
    /* Thread A */
    use_all_buffers();
    
    /* Thread B */
    functionB();
    이 애플리케이션에는 두 개의 스레드 ( thread Athread B) 가 있습니다. Thread B 가 먼저 실행되기 시작하면 thread A 이 곧 시작됩니다. thread Ause_all_buffers () 를 실행하고 A.mutex를 성공적으로 잠금하면 thread B 이 (가) 이미 잠갔기 때문에 B.mutex을 (를) 잠그려고 할 때 블록이 블록됩니다. thread BfunctionB 를 실행하고 thread A 가 차단되는 동안 some_condition 가 실행되는 동안 thread Bthread A에 의해 이미 잠겨 있는 A.mutex를 획득하려고 시도하는 것을 차단합니다. 이로 인해 교착 상태가 됩니다.

    이 교착 상태에 대한 해결책은 자원을 사용하기 전에 각 스레드에서 필요한 자원 잠금을 모두 확보하는 것입니다. 잠금을 확보할 수 없으면 잠금을 해제한 후 다시 시작해야 합니다.

뮤텍스 및 레이스 조건

뮤텍스(Mutual exclusion locks)는 레이스 조건으로 인한 데이터 불일치를 방지할 수 있습니다. 레이스 조건은 둘 이상의 스레드가 동일한 메모리 영역에서 조작을 수행해야 할 경우에 종종 발생하지만, 계산 결과는 이러한 조작이 수행되는 순서에 따라 달라집니다.

예를 들어, 두 개의 스레드인 A및 B에 의해 증가되는 단일 카운터 X를 고려하십시오. X 가 원래 1이면, 스레드 A및 B가 카운터를 증가시키면 X 는 3이어야 합니다. 두 스레드는 독립 엔터티이며 서로에 대해 동기화되지 않습니다. C문이긴 하지만X++다음 의사 어셈블러 코드에 표시된 대로 생성된 어셈블리 코드가 아닐 수 있습니다.
move    X, REG
inc     REG
move    REG, X

앞의 예제에서 두 스레드 모두가 두 개의 CPU에서 동시에 실행되는 경우 또는 스케줄링이 각 명령어에서 스레드를 교대로 실행하는 경우에 다음 단계가 발생할 수 있습니다.

  1. 스레드 A가 첫 번째 명령어를 실행하고 1인 X를 스레드 A 레지스터에 넣습니다. 그런 다음, 스레드 B가 실행되고 1인 X를 스레드 B 레지스터에 삽입합니다. 다음 예제는 결과 레지스터와 메모리 X의 내용을 나타냅니다.
    Thread A Register = 1
    Thread B Register = 1
    Memory X          = 1
  2. 스레드 A는 제2명령어를 실행하고, 그 레지스터의 콘텐츠를 2로 증분시킨다. 그 후, 쓰레드 B는 그 레지스터를 2로 증가시킨다. 메모리 X에 이동된 것이 없으므로 메모리 X는 그대로 유지됩니다. 다음 예제는 결과 레지스터와 메모리 X의 내용을 나타냅니다.
    Thread A Register = 2
    Thread B Register = 2
    Memory X          = 1
  3. 스레드 A는 현재 2인 레지스터의 컨텐츠를 메모리 X로 이동합니다. 그런 다음 스레드 B는 레지스터의 컨텐츠를 메모리 X로 이동하여 스레드 A의 값을 겹쳐씁니다. 다음 예제는 결과 레지스터와 메모리 X의 내용을 나타냅니다.
    Thread A Register = 2
    Thread B Register = 2
    Memory X          = 2

대부분의 경우, thread A와 thread B는 교차 방식으로 세 개의 명령어를 실행하고, 결과는 예상대로 3입니다. 레이스 조건은 주기적으로 발생하기 때문에 대개 발견하기가 어렵습니다.

이 레이스 조건을 피하려면 카운터에 액세스하고 메모리 X를 갱신하기 전에 각 스레드가 데이터를 잠궈야 합니다. 예를 들어, 스레드 A가 잠금을 취하고 카운터를 갱신하는 경우, 값이 2인 메모리 X 가 남아 있습니다. 스레드 A가 잠금을 해제한 후, 스레드 B는 잠금을 취하고 카운터를 갱신하며, 2를 X 의 초기 값으로 사용하여 예상 결과를 3으로 증가합니다.