재귀 잠금 (Mutex) vs 비 재귀 잠금 (Mutex)


183

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

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

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

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

답변:


154

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

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

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

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

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

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

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


9
비 재귀 뮤텍스에 대한 당신의 설명은 세마포어처럼 들렸습니다. 뮤텍스 (재귀 적이든 비재 귀적이든)는 소유권 개념을 가지고 있습니다.
Jay D

@JayD 사람들이 이런 것들에 대해 논쟁 할 때 매우 혼란 스러워요. 그렇다면 누가 이것을 정의하는 주체입니까?
Pacerier

13
@Pacerier 관련 표준. 이 답변은 예를 들어 posix (pthreads)에 대해 잘못되었습니다.이 스레드를 잠근 스레드 이외의 스레드에서 일반 뮤텍스를 잠금 해제하면 정의되지 않은 동작이며 오류 검사 또는 재귀 뮤텍스와 동일하게 수행하면 예상 가능한 오류 코드가 발생합니다. 다른 시스템 및 표준은 매우 다르게 작동 할 수 있습니다.
nos

아마도 이것은 순진하지만, 뮤텍스의 중심 아이디어는 잠금 스레드가 뮤텍스를 잠금 해제하고 다른 스레드가 동일하게 할 수 있다는 인상을 받았습니다. 에서 computing.llnl.gov/tutorials/pthreads :
user657862

2
@curiousguy-브로드 캐스트 릴리스는 세마포어에서 차단 된 모든 스레드를 명시 적으로 제공하지 않고 비 웁니다 (비어 있음). 일반적인 바이너리 제공은 대기 큐의 헤드에서만 스레드를 해제합니다 (차단 된 것으로 가정).
키 큰 Jeff

123

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

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

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

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

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


4
자물쇠를 두 번 잡을 때 변하지 않는 것에 대해 말한 것을 얻는 데 시간이 걸렸습니다. 좋은 지적! 읽기 쓰기 잠금 (예 : Java의 ReadWriteLock)이고 읽기 잠금을 획득 한 후 동일한 스레드에서 두 번째로 읽기 잠금을 다시 획득 한 경우 어떻게해야합니까? 읽기 잠금 권한을 얻은 후에 불변을 무효화하지 않습니까? 따라서 두 번째 읽기 잠금을 획득해도 변하지 않습니다.
dgrant

1
@Jonathan Java는 요즘 java.util.concurrent.locks에서 재진입 금지 잠금을 가지고 있습니까 ??
user454322

1
+1 재진입 잠금에 가장 일반적으로 사용되는 것은 단일 클래스 내부에서 사용되는 것으로 보이며 보호 된 코드 조각과 보호되지 않은 코드 조각에서 일부 메서드를 호출 할 수 있습니다. 실제로 항상 고려할 수 있습니다. @ user454322 물론 Semaphore입니다.
maaartinus

1
내 오해를 용서하지만 이것이 뮤텍스와 어떤 관련이 있는지 모르겠습니다. 멀티 스레딩 및 잠금이 필요하지 않다고 가정하고을 A::foo()호출하기 전에 객체가 일관성이없는 상태로 남아있을 수 있습니다 A::bar(). 재귀 적이든 아니든 뮤텍스는이 경우와 어떤 관련이 있습니까?
Siyuan Ren

1
@SiyuanRen : 문제는 코드에 대해 로컬로 추론 할 수 있습니다. 사람들 (적어도 나)은 잠금 영역을 고정 유지 관리로 인식하도록 훈련됩니다. 즉, 잠금을 획득 할 때 다른 스레드가 상태를 수정하지 않으므로 임계 영역의 불변이 유지됩니다. 이것은 어려운 규칙이 아니며 불변을 염두에 두지 않고 코드를 작성할 수는 있지만 코드를 추론하고 유지하기가 더 어려워집니다. 뮤텍스가없는 단일 스레드 모드에서도 동일하게 발생하지만 보호 된 영역 주변에서 로컬로 추론하도록 훈련되지 않았습니다.
David Rodríguez-dribeas

92

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

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


9
Butenhof의 답변에서 마지막 부분을 참고하십시오. ...you're not DONE until they're [recursive mutex] all gone.. Or sit back and let someone else do the design.
user454322

2
또한 단일 스레드 재귀 뮤텍스를 사용하면 멀티 스레드 코드에서 외부 라이브러리의 사용을 시작할 때 외부 라이브러리의 불일치를 이해하는 노력을 의식적으로 연기하는 목발로 괜찮다고 말합니다. 그러나 목발을 영원히 사용해서는 안되지만 결국 코드의 동시성 불변성을 이해하고 수정하는 데 시간을 투자하십시오. 따라서 우리는 재귀 뮤텍스를 사용하는 것이 기술적 부채라고 역설 할 수 있습니다.
FooF

13

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

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

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

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


진실. 불변을 보호하는 더 나은 메커니즘이 있습니다.
ActiveTrayPrntrTagDataStrDrvr

8
이것은 그 진술을 제공 한 답변에 대한 의견이어야합니다. 뮤텍스는 데이터를 보호 할뿐만 아니라 변이체도 보호합니다. 뮤텍스 대신 원자 (데이터가 스스로를 보호하는 위치) 측면에서 간단한 컨테이너 (가장 단순한 스택)를 작성하면 문장을 이해할 수 있습니다.
David Rodríguez-dribeas

뮤텍스는 데이터를 보호하지 않으며 불변성을 보호합니다. 그 불변은 데이터를 보호하는 데 사용될 수 있습니다.
존 한나

4

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


4

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

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

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


1

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

그들은 무언가를하기 전에 뮤텍스가 잠금 해제 되었는지 확인해야 할 때 절대적으로 좋습니다 . 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재귀했다, 단순히 없을 것이다 방법 전화를하기 전에 반드시이 해제되어 있는지 확인합니다.

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