[multithreading] 재귀 잠금 (Mutex) vs 비 재귀 잠금 (Mutex)

POSIX를 사용하면 뮤텍스를 재귀 적으로 사용할 수 있습니다. 즉, 동일한 스레드가 동일한 뮤텍스를 두 번 잠글 수 있으며 교착 상태가 발생하지 않습니다. 물론 두 번 잠금을 해제해야합니다. 그렇지 않으면 다른 스레드가 뮤텍스를 얻을 수 없습니다. pthread를 지원하는 모든 시스템이 재귀 뮤텍스도 지원하지는 않지만 POSIX를 준수 하려면을 준수해야합니다 .

다른 API (더 높은 수준의 API)도 일반적으로 잠금이라고하는 뮤텍스를 제공합니다. 일부 시스템 / 언어 (예 : Cocoa Objective-C)는 재귀 및 비 재귀 뮤텍스를 모두 제공합니다. 일부 언어는 하나 또는 다른 언어 만 제공합니다. 예를 들어 Java 뮤텍스는 항상 재귀 적입니다 (같은 스레드가 같은 객체에서 두 번 “동기화”할 수 있음) 그들이 제공하는 다른 스레드 기능에 따라 재귀 뮤텍스가없는 것은 쉽게 직접 작성할 수 있기 때문에 아무런 문제가 없을 수 있습니다 (이미 더 간단한 뮤텍스 / 조건 작업을 기반으로 재귀 뮤텍스를 이미 구현했습니다).

내가 정말로 이해하지 못하는 것 : 비 재귀 뮤텍스는 무엇인가? 동일한 뮤텍스를 두 번 잠그면 스레드 교착 상태가 발생하는 이유는 무엇입니까? 이를 피할 수있는 고급 언어조차도 (예를 들어 교착 상태가되는지 테스트하고 예외가 발생하면 예외를 던지는 경우) 일반적으로 그렇게하지 않습니다. 대신 스레드 교착 상태가 발생합니다.

실수로 두 번 잠그고 한 번만 잠금을 해제하고 재귀 뮤텍스의 경우 문제를 찾기가 더 어려워서 대신 잘못된 잠금이 나타나는 위치를 즉시 교착 상태에 빠뜨릴 수 있습니까? 그러나 잠금을 해제 할 때와 마지막 잠금을 해제하고 카운터가 0이 아닌 상황에서 잠금 카운터를 반환하는 것과 같은 작업을 수행 할 수 없었습니다. 예외를 던지거나 문제를 기록 할 수 있습니까? 아니면 내가 볼 수없는 비 재귀 뮤텍스의 다른 유용한 유스 케이스가 있습니까? 비 재귀 뮤텍스가 재귀 뮤텍스보다 약간 빠를 수 있기 때문에 성능일까요? 그러나 나는 이것을 테스트했고 그 차이는 실제로 그렇게 크지 않습니다.



답변

재귀 뮤텍스와 비 재귀 뮤텍스의 차이점은 소유권과 관련이 있습니다. 재귀 뮤텍스의 경우, 커널은 실제로 처음으로 뮤텍스를 얻은 스레드를 추적하여 재귀와 다른 스레드 사이의 차이를 대신 차단해야합니다. 다른 대답에서 지적 했듯이이 컨텍스트를 저장하는 메모리와 유지 관리에 필요한주기 측면에서 추가 오버 헤드에 대한 문제가 있습니다.

그러나 여기서도 고려해야 할 다른 사항이 있습니다.

재귀 뮤텍스에는 소유권이 있기 때문에 뮤텍스를 잡는 스레드는 뮤텍스를 해제하는 스레드와 같아야합니다. 비 재귀 뮤텍스의 경우 소유권 감각이 없으며 어떤 스레드가 원래 어떤 뮤텍스를 취했는지에 관계없이 일반적으로 뮤텍스를 해제 할 수 있습니다. 대부분의 경우,이 유형의 “뮤텍스”는 실제로 세마포어 동작에 가깝습니다. 여기서 반드시 뮤텍스를 제외 장치로 사용할 필요는 없지만 둘 이상의 스레드간에 동기화 또는 신호 장치로 사용합니다.

뮤텍스에 소유권이있는 또 다른 속성은 우선 순위 상속을 지원하는 능력입니다. 커널은 뮤텍스를 소유 한 스레드와 모든 차단기의 신원을 추적 할 수 있기 때문에 우선 순위 스레드 시스템에서 현재 뮤텍스를 소유 한 스레드의 우선 순위를 우선 순위가 높은 스레드의 우선 순위로 에스컬레이션 할 수 있습니다. 현재 뮤텍스를 차단하고 있습니다. 이러한 상속은 그러한 경우에 발생할 수있는 우선 순위 반전 문제를 방지합니다. (모든 시스템이 그러한 뮤텍스에 대한 우선 순위 상속을 지원하지는 않지만 소유권 개념을 통해 가능 해지는 또 다른 기능입니다).

클래식 VxWorks RTOS 커널을 참조하면 세 가지 메커니즘을 정의합니다.

  • mutex- 재귀를 지원하고 선택적으로 우선 순위 상속을 지원합니다. 이 메커니즘은 일반적으로 중요한 데이터 섹션을 일관된 방식으로 보호하는 데 사용됩니다.
  • 이진 세마포어 -재귀, 상속 없음, 간단한 배제, 수취인 및주는 사람이 동일한 스레드 일 필요는 없으며 브로드 캐스트 릴리스가 가능합니다. 이 메커니즘은 중요한 섹션을 보호하는 데 사용될 수 있지만 스레드 간의 코 히어 런트 신호 또는 동기화에도 특히 유용합니다.
  • 세마포 계산 -재귀 또는 상속 없음, 원하는 초기 카운트에서 일관된 리소스 카운터 역할을하며 스레드는 리소스에 대한 순 카운트가 0 인 블록 만 차단합니다.

다시 말하지만, 이것은 플랫폼에 따라 다소 다르며, 특히 그들이 부르는 것입니다. 그러나 이것은 개념과 다양한 메커니즘을 대표해야합니다.


답변

대답은 효율성 이 아닙니다 . 재진입이 아닌 뮤텍스는 더 나은 코드로 이어집니다.

예 : A :: foo ()가 잠금을 획득합니다. 그런 다음 B :: bar ()를 호출합니다. 당신이 그것을 쓸 때 잘 작동했습니다. 그러나 나중에 누군가가 B :: bar ()를 변경하여 A :: baz ()를 호출하여 잠금을 얻습니다.

재귀 뮤텍스가 없다면이 교착 상태입니다. 그것들이 있으면 실행되지만 깨질 수 있습니다. A :: foo ()는 baz ()가 뮤텍스를 획득하기 때문에 실행할 수 없다는 가정하에 bar ()를 호출하기 전에 객체가 일관성이없는 상태로 남아있을 수 있습니다. 그러나 아마 실행해서는 안됩니다! A :: foo ()를 쓴 사람은 아무도 동시에 A :: baz ()를 호출 할 수 없다고 가정했습니다. 이것이 두 방법 모두 잠금을 획득 한 전체 이유입니다.

뮤텍스 사용을위한 올바른 정신 모델 : 뮤텍스는 불변을 보호합니다. 뮤텍스가 유지되면 불변은 변할 수 있지만, 뮤텍스를 해제하기 전에 불변이 다시 설정됩니다. 재진입 잠금은 잠금을 두 번째로 얻을 때 불변이 더 이상 사실인지 확신 할 수 없으므로 위험합니다.

재진입 잠금에 만족하는 경우 이전에 이와 같은 문제점을 디버그하지 않아도 되었기 때문입니다. Java는 요즘 java.util.concurrent.locks에 재진입이 불가능한 잠금을 가지고 있습니다.


답변

Dave Butenhof 자신이 작성한 것처럼 :

“재귀 뮤텍스의 모든 큰 문제 중 가장 큰 문제는 잠금 방식과 범위를 완전히 잃을 것을 권장한다는 것입니다. 이것은 치명적입니다. 사악합니다.”스레드 먹는 사람 “입니다. 가능한 한 가장 짧은 시간 동안 잠금을 유지합니다. 마침표 항상 보유하고 있음을 모르거나 호출자가 뮤텍스를 필요로하는지 여부를 모르기 때문에 잠금을 유지 한 상태로 전화를 걸면 너무 길게 잡고있는 것입니다. 응용 프로그램에 샷건을 조준하고 트리거를 당기는 것입니다. 동시성을 얻기 위해 스레드를 사용하기 시작했을 것입니다. 그러나 동시성을 막았습니다. “


답변

뮤텍스 사용을위한 올바른 정신 모델 : 뮤텍스는 불변을 보호합니다.

이것이 뮤텍스를 사용하기에 정말로 올바른 정신 모델인지 왜 확신하십니까? 올바른 모델이 데이터를 보호하지만 변하지 않는 것은 아니라고 생각합니다.

불변을 보호하는 문제는 단일 스레드 응용 프로그램에서도 나타나며 멀티 스레딩 및 뮤텍스와 공통점이 없습니다.

또한 불변량을 보호 해야하는 경우 여전히 이진 세마포어를 사용할 수 있습니다.


답변

재귀 뮤텍스가 유용한 한 가지 주요 이유는 동일한 스레드로 메소드에 여러 번 액세스하는 경우입니다. 예를 들어, 뮤텍스 락이 인출을 위해 은행 A / C를 보호하고 있다면, 그 인출과 관련된 수수료가 있으면 동일한 뮤텍스를 사용해야합니다.


답변

재귀 뮤텍스의 유일한 유스 케이스는 오브젝트에 여러 메소드가 포함 된 경우입니다. 메소드 중 하나가 오브젝트의 컨텐츠를 수정하므로 상태가 다시 일관성을 유지하기 전에 오브젝트를 잠 가야합니다.

메소드가 다른 메소드를 사용하는 경우 (예 : addNewArray ()가 addNewPoint ()를 호출하고 recheckBounds ()로 마무리) 해당 함수 자체가 뮤텍스를 잠 가야하는 경우 재귀 뮤텍스가 승리합니다.

다른 경우 (나쁜 코딩을 해결하고 다른 객체에서도 사용)는 분명히 잘못되었습니다!


답변

비 재귀 뮤텍스는 무엇입니까?

그들은 무언가를하기 전에 뮤텍스가 잠금 해제 되었는지 확인해야 할 때 절대적으로 좋습니다 . pthread_mutex_unlock뮤텍스가 재귀 적이 지 않은 경우에만 잠금 해제되도록 보장 할 수 있기 때문 입니다.

pthread_mutex_t      g_mutex;

void foo()
{
    pthread_mutex_lock(&g_mutex);
    // Do something.
    pthread_mutex_unlock(&g_mutex);

    bar();
}

경우 g_mutex비 재귀, 위의 코드는 호출에 보장 bar()뮤텍스와 잠금 해제 .

따라서 bar()다른 스레드가 동일한 뮤텍스를 얻으려고 시도 할 수있는 무언가를 수행 할 수있는 알 수없는 외부 함수 인 경우 교착 상태 가능성을 제거하십시오 . 이러한 시나리오는 스레드 풀을 기반으로하는 응용 프로그램 및 분산 응용 프로그램에서는 드문 일이 아니며, 프로세스 간 호출은 클라이언트 프로그래머가이를 인식하지 않고도 새 스레드를 생성 할 수 있습니다. 이러한 모든 시나리오에서 잠금이 해제 된 후에 만 ​​해당 외부 기능을 호출하는 것이 가장 좋습니다.

경우 g_mutex재귀했다, 단순히 없을 것이다 방법 전화를하기 전에 반드시이 해제되어 있는지 확인합니다.