뮤텍스가 잠겨 있는지 확인할 수없는 이유는 무엇입니까?


28

C ++ 14 std::mutex는 잠금 여부를 확인하는 메커니즘을 생략 한 것으로 보입니다 . 이 SO 질문을 참조하십시오 :

https://stackoverflow.com/questions/21892934/how-to-assert-if-a-stdmutex-is-locked

이를 해결하는 방법에는 여러 가지가 있습니다.

std::mutex::try_lock()
std::unique_lock::owns_lock()

그러나 이들 중 어느 것도 특히 만족스러운 해결책은 아닙니다.

try_lock()현재 스레드가 뮤텍스를 잠근 경우 거짓 음수를 반환하고 정의되지 않은 동작을 갖습니다. 또한 부작용이 있습니다. 원본 위에 owns_lock()건설이 필요합니다 .unique_lockstd::mutex

분명히 내 자신을 굴릴 수는 있지만 현재 인터페이스의 동기를 이해하고 싶습니다.

뮤텍스 (예 :)의 상태를 확인하는 기능 std::mutex::is_locked()은 난해한 요청처럼 보이지 않으므로 표준위원회가이 기능을 감독하는 것이 아니라 의도적으로 생략 한 것 같습니다.

왜?

편집 : 좋아, 아마도이 유스 케이스는 내가 예상했던 것처럼 일반적이지 않으므로 특정 시나리오를 설명 할 것입니다. 여러 스레드에 배포되는 기계 학습 알고리즘이 있습니다. 각 스레드는 비동기 적으로 작동하며 최적화 문제가 완료되면 마스터 풀로 돌아갑니다.

그런 다음 마스터 뮤텍스를 잠급니다. 그런 다음 스레드는 자손을 돌연변이시킬 새 부모를 선택해야하지만 현재 다른 스레드에 의해 최적화되는 자손이없는 부모에서만 선택할 수 있습니다. 따라서 현재 다른 스레드에 의해 잠겨 있지 않은 부모를 찾으려면 검색을 수행해야합니다. 마스터 스레드 뮤텍스가 잠겨 있기 때문에 검색 중에 뮤텍스 상태가 변경 될 위험이 없습니다. 분명히 다른 솔루션 (현재 부울 플래그를 사용하고 있음)이 있지만 뮤텍스가 스레드 간 동기화를 위해 존재하기 때문에이 문제에 대한 논리적 솔루션을 제공한다고 생각했습니다.


42
확인 후 1 나노초 후에 잠금 해제되거나 잠길 수 있으므로 뮤텍스가 잠겨 있는지 여부를 실제로 합리적으로 확인할 수 없습니다. 따라서 "if (mutex_is_locked ()) ..."를 쓴 경우 mutex_is_locked는 올바른 결과를 반환 할 수 있지만 "if"가 실행될 때 잘못된 것입니다.
gnasher729

1
이것은 ^. 어떤 유용한 정보를 얻으 is_locked시겠습니까?
쓸모없는

3
이것은 XY 문제처럼 느껴집니다. 왜 자녀가 생성되는 동안에 만 부모의 재사용을 막으려 고 노력합니까? 당신은 어떤 부모라도 정확히 하나의 자손을 가질 수 있어야합니까? 당신의 자물쇠는 그것을 막을 수 없습니다. 당신은 명확한 세대가 없습니까? 그렇지 않다면 더 빨리 최적화 할 수있는 개인이 더 자주 /보다 일찍 선택 될 수 있기 때문에 체력이 더 높다는 것을 알고 있습니까? 세대를 사용하는 경우 모든 부모를 먼저 선택하고 스레드가 대기열에서 부모를 검색하도록 하시겠습니까? 자손 생성이 실제로 너무 비싸서 여러 스레드가 필요합니까?
amon

10
@quant-예제 응용 프로그램의 부모 객체 뮤텍스가 전혀 뮤텍스 여야하는 이유를 알 수 없습니다. 설정 할 때마다 잠겨있는 마스터 뮤텍스가있는 경우 부울 변수를 사용하여 상태를 나타낼 수 있습니다.
Periata Breatta

4
질문의 마지막 문장에 동의하지 않습니다. 간단한 부울 값은 뮤텍스보다 훨씬 깨끗합니다. 부모를 "반환"하기 위해 마스터 뮤텍스를 잠그지 않으려면 원자 부울로 만드십시오.
Sebastian Redl

답변:


53

제안 된 작업에 적어도 두 가지 심각한 문제가 있음을 알 수 있습니다.

첫 번째는 이미 @ gnasher729 의 주석에서 언급되었습니다 .

확인 후 1 나노초 후에 잠금 해제되거나 잠길 수 있으므로 뮤텍스가 잠겨 있는지 여부를 실제로 합리적으로 확인할 수 없습니다. 당신이 쓴 그래서 만약 if (mutex_is_locked ()) …다음 mutex_is_locked올바른 결과를 반환하지만,이 시간에 의해 수있는 if실행, 그것은 잘못된 것입니다.

뮤텍스의 "현재 잠금 상태"속성이 변경되지 않도록하는 유일한 방법은 직접 잠그는 것입니다.

두 번째 문제는 뮤텍스를 잠그지 않으면 스레드가 이전에 뮤텍스를 잠근 스레드와 동기화되지 않는다는 것입니다. 따라서“전”과“후”에 대해 말하는 것이 명확하지 않으며 뮤텍스가 잠겨 있는지 여부는 Schrödiger의 고양이가 상자를 열지 않고 현재 살아 있는지 묻고 있습니다.

내가 올바르게 이해하면 마스터 뮤텍스가 잠겨있어 두 가지 문제가 특정 경우에 문제가 될 수 있습니다. 그러나 이것은 나에게 특히 일반적인 경우처럼 보이지 않으므로위원회는 매우 특별한 시나리오에서 다소 유용 할 수있는 기능을 추가하지 않고 올바른 일을했다고 생각합니다. ( "인터페이스를 올바르게 사용하기 쉽고 잘못 사용하기 어렵게 만드십시오."

그리고 내가 말할 수 있다면, 현재 가지고있는 설정이 가장 우아하지 않으며 문제를 피하기 위해 리팩토링 될 수 있다고 생각합니다. 예를 들어, 마스터 스레드가 현재 잠기지 않은 모든 부모를 검사하는 대신 준비된 부모 대기열을 유지하지 않는 이유는 무엇입니까? 스레드가 다른 스레드를 최적화하려는 경우 다음 스레드를 큐에서 팝하고 새 부모가있는 즉시 큐에 추가합니다. 이렇게하면 코디네이터로 마스터 스레드가 필요하지 않습니다.


고마워, 이것은 좋은 대답입니다. 준비된 부모의 대기열을 유지하고 싶지 않은 이유는 부모가 생성 된 순서를 유지해야하기 때문입니다 (이는 수명을 나타냅니다). 이것은 LIFO 대기열로 쉽게 수행됩니다. 내가 물건을 챙겨 나가기 시작하면 물건을 복잡하게하는 별도의 순서 메커니즘을 유지해야하므로 현재 접근 방식이 필요합니다.
quant

14
@quant : 당신이 큐 부모에게 두 가지 목적이있는 경우에 그렇게 할 수 있습니다 .... 큐

@quant : 항목을 한 번 (최대) 삭제하지만 아마도 여러 번 처리 할 것으로 예상되므로 일반적인 경우를 제외하고는 드문 경우를 최적화하고 있습니다. 이것은 거의 바람직하지 않습니다.
Jerry Coffin

2
그러나 현재 스레드가 뮤텍스를 잠 갔는지 묻는 것이 합리적입니다.
제한 속죄

@LimitedAtonement 실제로는 아닙니다. 이 뮤텍스는 추가 정보 (스레드 ID)를 저장해야하므로 속도가 느려집니다. 재귀 뮤텍스는 이미 이것을하고 있습니다. 대신 대신해야합니다.
StaceyGirl

9

2 차 뮤텍스를 사용하여 최적화 문제에 대한 액세스를 잠그지 않고 최적화 문제가 지금 최적화되고 있는지 여부를 결정하는 것 같습니다.

그것은 완전히 불필요합니다. 최적화가 필요한 문제 목록, 지금 최적화중인 문제 목록 및 최적화 된 문제 목록이 있습니다. 문자 그대로 "목록"을 사용하지 말고 "적절한 데이터 구조"를 의미하십시오.

최적화되지 않은 문제 목록에 새 문제를 추가하거나 한 목록에서 다음 목록으로 문제를 이동하는 작업은 단일 "마스터"뮤텍스의 보호하에 수행됩니다.


1
유형의 객체가 std::mutex그러한 데이터 구조에 적합 하다고 생각하지 않습니까?
quant

2
@quant-아니요. std::mutex할당 및 / 또는 작동이 제한되고 느린 리소스 (예 : 핸들)를 가져갈 수있는 운영 체제 정의 뮤텍스 구현에 의존합니다. 내부 데이터 구조에 대한 액세스를 잠그기 위해 단일 뮤텍스를 사용하는 것이 훨씬 더 효율적이며 확장 성이 뛰어납니다.
Periata Breatta

1
조건 변수도 고려하십시오. 그들은 이와 같은 많은 데이터 구조를 정말 쉽게 만들 수 있습니다.
Cort

2

다른 사람들이 말했듯이 유스 케이스는 없습니다. is_locked 뮤텍스에서 이점이 는 없으므로 기능이 존재하지 않습니다.

문제가있는 경우는 매우 흔합니다. 기본적으로 작업자 스레드가 수행하는 작업입니다 . 스레드의 가장 일반적인 구현을.

10 개의 상자가있는 선반이 있습니다. 이 상자를 다루는 작업자는 4 명입니다. 4 명의 작업자가 다른 상자에서 작업하게하려면 어떻게해야합니까? 첫 번째 작업자는 작업을 시작하기 전에 선반에서 상자를 꺼냅니다. 두 번째 작업자는 선반에 9 개의 상자가 보입니다.

상자를 잠그는 뮤텍스가 없으므로 상자에서 가상 뮤텍스의 상태를 볼 필요가 없으며 부울로 뮤텍스를 악용하는 것은 잘못입니다. 뮤텍스가 선반을 잠급니다.


1

위의 5gon12eder의 답변에 주어진 두 가지 이유 외에도, 나는 그것이 필요하거나 바람직하지 않다고 덧붙이고 싶습니다.

이미 뮤텍스를 잡고 있다면, 뮤텍스를 잡고 있다는 것을 더 잘 알고있었습니다! 묻지 않아도됩니다. 메모리 블록이나 다른 리소스를 소유하는 것과 마찬가지로 소유 여부와 리소스를 해제 / 삭제하는 것이 적절한시기를 정확히 알아야합니다.
그렇지 않은 경우 프로그램이 잘못 설계되었으며 문제가 발생할 수 있습니다.

뮤텍스에 의해 보호되는 공유 리소스에 액세스해야하고 뮤텍스를 아직 보유하고 있지 않은 경우 뮤텍스를 획득해야합니다. 다른 옵션이 없습니다. 그렇지 않으면 프로그램 논리가 올바르지 않습니다.
당신은 어느 경우에, 허용 또는 inacceptable 차단 찾을 수 있습니다 lock()또는 try_lock()당신이 원하는 동작을 제공 할 것입니다. 의심 할 여지없이 뮤텍스를 성공적으로 획득했는지 여부 (반환 값이 try_lock알려짐) 만 알면 됩니다. 다른 사람이 소유하고 있는지 또는 가짜 실패를했는지 여부는 중요하지 않습니다.

다른 모든 경우에있어서, 그것은 당신의 사업이 아닙니다. 알 필요가 없으며 알지 못하거나 가정하지 않아야합니다 (다른 질문에서 언급 된 적시성 및 동기화 문제에 대해).


1
현재 잠금에 사용 가능한 리소스에 대해 순위 지정 작업을 수행하려면 어떻게합니까?
quant

그러나 그것이 현실적으로 일어날 수있는 일입니까? 나는 다소 이례적인 것으로 생각합니다. 두 리소스 중 하나에 이미 고유 한 순위가 있다고 말하면 더 중요한 것을 먼저 수행해야합니다 (잠금 획득). 예 : 렌더링 전에 물리 시뮬레이션을 업데이트해야합니다. 또는 순위가 다소 고의적 인 try_lock경우 첫 번째 리소스 뿐만 아니라 두 번째 리소스가 실패하면 두 번째 리소스를 사용해보십시오. 예 : 데이터베이스 서버에 대한 3 개의 지속적 풀링 된 연결이며, 하나를 사용하여 명령을 보내야합니다.
데이먼

4
@quant- "현재 잠금 가능한 자원에 대한 순위 작업"-일반적으로 이런 종류의 작업은 이해하기 어려운 방식으로 교착 상태에있는 코드를 작성하는 매우 쉽고 빠른 방법입니다. 거의 모든 경우에있어 잠금 획득 및 해제 결정 은 최상의 정책입니다. 변경 될 수있는 기준에 따라 잠금을 검색하면 문제가 발생합니다.
Periata Breatta

@PeriataBreatta 프로그램이 의도적으로 확정되지 않았습니다. 나는이 속성이 일반적이지 않다는 것을 알았으므로 is_locked()그러한 행동을 용이하게 할 수 있는 기능의 생략을 이해할 수 있습니다 .
quant

@quant 순위와 잠금은 완전히 별개의 문제입니다. 잠금을 사용하여 대기열을 정렬하거나 순서를 바꾸려면 잠그고 정렬 한 다음 잠금을 해제하십시오. 당신이 필요하다면 is_locked, 당신 의 생각보다 훨씬 더 나은 해결책이 존재합니다.
피터

1

기본 메모리 순서로 atomic_flag 를 사용 하려고 할 수 있습니다 . 데이터 레이스가 없으며 mutex처럼 여러 잠금 해제 호출에서 예외를 throw하지 않습니다 (그리고 제어 할 수 없게 중단됩니다 ...). 또는 원자 (예 : atomic [bool] 또는 atomic [int] ([]가 아닌 삼각형 괄호 사용))가 있으며 load 및 compare_exchange_strong과 같은 훌륭한 기능이 있습니다.


1

이를 위해 유스 케이스를 추가하고 싶습니다. 내부 기능을 사용하면 호출자가 실제로 잠금을 유지하고 있음을 전제 조건으로 주장 할 수 있습니다.

내부 함수가 여러 개인 클래스와이를 호출하는 많은 공용 함수의 경우 내부 함수를 호출하는 다른 공용 함수를 추가하는 사람이 실제로 잠금을 획득했는지 확인할 수 있습니다.

class SynchronizedClass
{

public:

void publicFunc()
{
  std::lock_guard<std::mutex>(_mutex);

  internalFuncA();
}

// A lot of code

void newPublicFunc()
{
  internalFuncA(); // whops, forgot to acquire the lock
}


private:

void internalFuncA()
{
  assert(_mutex.is_locked_by_this_thread());

  doStuffWithLockedResource();
}

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