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


92

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

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

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

답변:


159

재진입 잠금

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

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

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

재진입 잠금의 사용 사례

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

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

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

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

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

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

재진입 뮤텍스

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

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


2
이러한 상황은 일반적으로 피해야하지만 교착 상태 등을 피하기가 어렵 기 때문입니다. 스레딩은 어쨌든 당신이 이미 자물쇠를 가지고 있는지 의심하지 않고 충분히 어렵습니다.
Jon Skeet

+1, 잠금이 재진입되지 않는 경우도 고려하십시오.주의하지 않으면 스스로 차단할 수 있습니다. 또한 C에서는 잠금이 획득 된 횟수만큼 해제되도록 다른 언어와 동일한 메커니즘이 없습니다. 이것은 큰 문제로 이어질 수 있습니다.
user7116

1
그것이 바로 어제 나에게 일어난 일입니다. 재진입 문제를 고려하지 않았고 결국 5 시간 동안 교착 상태를 디버깅했습니다 ...
vehomzzz

@Jon Skeet-성능이나 기타 고려 사항으로 인해 잠금을 추적하는 것이 비현실적인 상황 (위의 다소 인위적인 예 참조)이있을 수 있다고 생각합니다.
ConcernedOfTunbridgeWells

21

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

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


이것은 동일한 잠금 obj를 두 번 이상 획득하는 재귀 호출이있는 경우- x주어진 스레드에 의해 시간이 말하면 모든 재귀 적으로 획득 한 잠금을 해제하지 않고 실행을 인터리브 할 수 없다는 것을 의미합니까 (동일한 잠금이지만 x여러 번)? 참이면 본질적으로이 구현을 순차적으로 만듭니다. 내가 뭔가를 놓치고 있습니까?
DevdattaK 2010

그것은 진짜 끔찍한 문제가 아니어야합니다. 그것은 세분화 된 잠금에 관한 것이며 스레드는 스스로 잠기지 않을 것입니다.
Henk Holterman


3

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

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


먼저 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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.