Programing

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

lottogame 2020. 10. 5. 07:27
반응형

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


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

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

Deadlock in thread t2, in func2. See? func3 finishes and exits, relocking does not block the thread or lead to deadlock.


So, last question, why do we need it ?

For recursive function (called in multi-threaded programs and you want to protect some resource/data).

E.g. You have a multi thread program, and call a recursive function in thread A. You have some data that you want to protect in that recursive function, so you use the mutex mechanism. The execution of that function is sequential in thread A, so you would definitely relock the mutex in recursion. Use normal mutex causes deadlocks. And resursive mutex is invented to solve this.

See an example from the accepted answer When to use recursive mutex?.

The Wikipedia explains the recursive mutex very well. Definitely worth for a read. Wikipedia: Reentrant_mutex

참고URL : https://stackoverflow.com/questions/1312259/what-is-the-re-entrant-lock-and-concept-in-general

반응형