[multithreading] 재진입 잠금 및 개념은 일반적으로 무엇입니까?

나는 항상 헷갈 린다. 누군가 재진입이 무엇인지 설명 할까요 이 다른 맥락에서 의미 있습니까? 재진입과 재진입이 아닌 이유는 무엇입니까?

pthread (posix) 잠금 프리미티브라고 말하면 재진입입니까? 그것들을 사용할 때 어떤 함정을 피해야합니까?

뮤텍스가 재진입 할 ​​수 있습니까?



답변

재진입 잠금

재진입 잠금은 프로세스가 자체적으로 차단하지 않고 잠금을 여러 번 요청할 수있는 잠금입니다. 이미 자물쇠를 잡았는지 여부를 추적하기가 쉽지 않은 상황에서 유용합니다. 잠금이 재진입이 아닌 경우 잠금을 잡은 다음 다시 잡으러 갈 때 차단하여 자신의 프로세스를 효과적으로 교착 상태로 만들 수 있습니다.

일반적으로 재진입은 코드가 실행되는 동안 호출되면 손상 될 수있는 중앙 변경 가능 상태가없는 코드의 속성입니다. 이러한 호출은 다른 스레드에 의해 수행되거나 코드 자체 내에서 시작된 실행 경로에 의해 재귀 적으로 수행 될 수 있습니다.

코드가 실행 중에 업데이트 될 수있는 공유 상태에 의존하는 경우 적어도 해당 업데이트가이를 중단시킬 수 있다면 재진입되지 않습니다.

재진입 잠금의 사용 사례

재진입 잠금에 대한 애플리케이션의 (다소 일반적이고 인위적인) 예는 다음과 같습니다.

  • 그래프를 가로 지르는 알고리즘과 관련된 계산이 있습니다 (아마도 그 안에 사이클 포함). 순회는주기 또는 동일한 노드에 대한 다중 경로로 인해 동일한 노드를 두 번 이상 방문 할 수 있습니다.

  • 데이터 구조는 동시 액세스의 영향을받으며 어떤 이유로 다른 스레드에 의해 업데이트 될 수 있습니다. 경합 상태로 인한 잠재적 인 데이터 손상을 처리하려면 개별 노드를 잠글 수 있어야합니다. 어떤 이유로 (아마도 성능) 전체 데이터 구조를 전체적으로 잠그고 싶지는 않습니다.

  • 귀하의 계산은 귀하가 방문한 노드에 대한 완전한 정보를 유지할 수 없거나 ‘이전에 여기에 있었던 적이 있습니까?’질문에 신속하게 답변 할 수없는 데이터 구조를 사용하고 있습니다.

    이 상황의 예는 간단한 연결 목록을 대기열로 사용하는 폭 우선 검색 또는 바이너리 힙으로 구현 된 우선 순위 대기열이있는 Dijkstra 알고리즘의 간단한 구현입니다. 이 경우 기존 삽입에 대한 대기열을 스캔하는 것은 O (N)이며 모든 반복에서 수행하지 않을 수 있습니다.

이 상황에서 이미 획득 한 잠금을 추적하는 것은 비용이 많이 듭니다. 노드 수준에서 잠금을 수행한다고 가정하면 재진입 잠금 메커니즘을 사용하면 이전에 노드를 방문했는지 여부를 알 필요가 없습니다. 노드를 맹목적으로 잠 그거나 대기열에서 빼낸 후에 잠금을 해제 할 수 있습니다.

재진입 뮤텍스

주어진 시간에 하나의 스레드 만 중요 섹션에있을 수 있으므로 단순 뮤텍스는 재진입이 아닙니다. 뮤텍스를 잡은 다음 다시 잡으려고하면 간단한 뮤텍스에는 이전에 누가 보유하고 있었는지 알 수있는 충분한 정보가 없습니다. 이를 재귀 적으로 수행하려면 각 스레드에 토큰이있는 메커니즘이 필요하므로 누가 뮤텍스를 획득했는지 알 수 있습니다. 이로 인해 뮤텍스 메커니즘이 다소 비싸므로 모든 상황에서 수행하고 싶지 않을 수 있습니다.

IIRC POSIX 스레드 API는 재진입 및 비 재진입 뮤텍스 옵션을 제공합니다.


답변

재진입 잠금을 사용하면 M리소스에 잠금을 설정 A한 다음 M재귀 적으로 또는 이미 잠금을 보유한 코드에서 호출 하는 메서드 를 작성할 수 있습니다 A.

재진입이 아닌 잠금을 사용하려면 두 가지 버전 M, 즉 잠그는 버전 과 그렇지 않은 버전 , 올바른 버전을 호출하는 추가 로직이 필요합니다.


답변

재진입 잠금은이 튜토리얼 에서 매우 잘 설명되어 있습니다 .

자습서의 예제는 그래프 횡단에 대한 답변보다 훨씬 덜 고안되었습니다. 재진입 잠금은 매우 간단한 경우에 유용합니다.


답변

재귀 뮤텍스 의 정의와 이유 는 수락 된 답변에 설명 된 그렇게 복잡한 는 안됩니다.

그물을 파고 나서 이해 한 내용을 적어보고 싶습니다.


먼저 mutex 에 대해 이야기 할 때 다중 스레드 개념도 확실히 관련되어 있음을 인식해야합니다 . (뮤텍스는 동기화에 사용됩니다. 프로그램에 스레드가 하나만 있으면 뮤텍스가 필요하지 않습니다.)


둘째, 일반 뮤텍스재귀 뮤텍스 의 차이점을 알아야합니다. .

APUE 에서 인용 :

(재귀 적 뮤텍스는 a) 동일한 스레드 가 먼저 잠금을 해제하지 않고 여러 번 잠글 수 있도록하는 뮤텍스 유형입니다 .

주요 차이점은 동일한 스레드 내에서 재귀 잠금을 다시 잠그면 교착 상태로 이어지지 않고 스레드를 차단하지 않는다는 것입니다.

이것은 반향 적 잠금이 교착 상태를 일으키지 않음을 의미합니까?
아니요, 잠금을 해제하지 않고 한 스레드에서 잠그고 다른 스레드에서 잠그려고하면 정상적인 뮤텍스처럼 교착 상태가 발생할 수 있습니다.

일부 코드를 증거로 보겠습니다.

  1. 교착 상태가있는 일반 뮤텍스
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock;


void * func1(void *arg){
    printf("thread1\n");
    pthread_mutex_lock(&lock);
    printf("thread1 hey hey\n");

}


void * func2(void *arg){
    printf("thread2\n");
    pthread_mutex_lock(&lock);
    printf("thread2 hey hey\n");
}

int main(){
    pthread_mutexattr_t lock_attr;
    int error;
//    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
    if(error){
        perror(NULL);
    }

    pthread_mutex_init(&lock, &lock_attr);

    pthread_t t1, t2;

    pthread_create(&t1, NULL, func1, NULL);
    pthread_create(&t2, NULL, func2, NULL);

    pthread_join(t2, NULL);

}

산출:

thread1
thread1 hey hey
thread2

일반적인 교착 상태 예, 문제 없습니다.

  1. 교착 상태가있는 재귀 뮤텍스

이 줄의 주석 처리를 제거
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
하고 다른 줄은 주석 처리하십시오 .

산출:

thread1
thread1 hey hey
thread2

예, 재귀 뮤텍스는 교착 상태를 일으킬 수도 있습니다.

  1. 일반 뮤텍스, 동일한 스레드에서 재 잠금
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

pthread_mutex_t lock;


void func3(){
    printf("func3\n");
    pthread_mutex_lock(&lock);
    printf("func3 hey hey\n");
}

void * func1(void *arg){
    printf("thread1\n");
    pthread_mutex_lock(&lock);
    func3();
    printf("thread1 hey hey\n");

}


void * func2(void *arg){
    printf("thread2\n");
    pthread_mutex_lock(&lock);
    printf("thread2 hey hey\n");
}

int main(){
    pthread_mutexattr_t lock_attr;
    int error;
//    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
    if(error){
        perror(NULL);
    }

    pthread_mutex_init(&lock, &lock_attr);

    pthread_t t1, t2;

    pthread_create(&t1, NULL, func1, NULL);
    sleep(2);
    pthread_create(&t2, NULL, func2, NULL);

    pthread_join(t2, NULL);

}

산출:

thread1
func3
thread2

교착 상태 thread t1func3.
( sleep(2)교착 상태가 처음에 다시 잠김으로 인해 발생했음을 쉽게 알 수 있도록 사용 합니다. func3)

  1. 재귀 뮤텍스, 동일한 스레드에서 재 잠금

다시, 재귀 뮤텍스 줄의 주석 처리를 제거하고 다른 줄은 주석 처리하십시오.

산출:

thread1
func3
func3 hey hey
thread1 hey hey
thread2

교착 상태 thread t2func2. 보다? func3완료 및 종료, 재 잠금은 스레드를 차단하거나 교착 상태로 이어지지 않습니다.


그래서, 마지막 질문, 왜 우리가 그것을 필요로합니까?

재귀 함수 (다중 스레드 프로그램에서 호출되며 일부 리소스 / 데이터를 보호하려는 경우)

예 : 다중 스레드 프로그램이 있고 스레드 A에서 재귀 함수를 호출합니다. 해당 재귀 함수에서 보호하려는 데이터가 있으므로 뮤텍스 메커니즘을 사용합니다. 해당 함수의 실행은 스레드 A에서 순차적이므로 재귀에서 뮤텍스를 확실히 다시 잠글 것입니다. 정상적인 뮤텍스를 사용하면 교착 상태가 발생합니다. 그리고 부활 뮤텍스 해결하기 위해 가 발명되었습니다.

허용 된 답변의 예를 참조하십시오.
When to use recursive mutex? .

Wikipedia는 재귀 뮤텍스를 매우 잘 설명합니다. 읽을만한 가치가 있습니다. Wikipedia : Reentrant_mutex


답변