2017-02-26 1 views
5

내가 std::condition_variable(lock,pred)의 VC++ 구현을 검토 한 결과, 기본적으로는 다음과 같습니다 : 기본적으로std :: condition_variable에서 가능한 경쟁 조건이 있습니까?

template<class _Predicate> 
     void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) 
     { // wait for signal and test predicate 
     while (!_Pred()) 
      wait(_Lck); 
     } 

, cond->_get_cv()->wait(cs);을 (호출 do_wait 호출 _Cnd_wait 호출 알몸 wait 전화 _Cnd_waitX이 모든이에를 파일 cond.c).

cond->_get_cv()Concurrency::details::stl_condition_variable_interface을 반환합니다. 우리는 파일 primitives.h에 가면

, 우리는 윈도우 7 이상에서, 우리는 좋은 오래된는 Win32 CONDITION_VARIABLEwait 전화 __crtSleepConditionVariableSRW를 포함하는 클래스 stl_condition_variable_win7있는 것을 알 수있다.

어셈블리 디버그 비트를 사용하면 __crtSleepConditionVariableSRWSleepConditionVariableSRW 함수 포인터를 추출하고 호출합니다.

내가 아는 한, win32 CONDITION_VARIABLE은 커널 개체가 아니라 사용자 모드 것입니다. 따라서 일부 스레드가이 변수를 알리고 실제로 스레드가 잠자기 상태가 아닌 경우 알림을 잃어 버리면 시간 초과가되거나 다른 스레드가 알림을 알릴 때까지 스레드가 잠자기 상태로 유지됩니다. 작은 프로그램이 실제로 그것을 증명할 수 있습니다. 통지 시점을 놓친 경우 다른 스레드가 알림을 보냈음에도 스레드는 잠자고 있습니다.

내 질문은 다음과 같습니다 :
하나의 스레드가 조건 변수에서 대기하고 조건부가 false를 반환합니다. 그런 다음 위에 설명 된 전체 호출 체인이 발생합니다. 그 시간에 다른 스레드가 환경을 변경하여 술어가 true를 리턴하도록 이 조건 변수를 알립니다. 우리는 원래 스레드의 술어를 전달했지만 여전히 호출 체인이 매우 길어 SleepConditionVariableSRW에 들어 가지 않았습니다.

따라서 우리는 조건 변수 을 통보했지만 조건 변수에 넣는 조건자는 분명히 true를 반환합니다 (알리미가 작성했기 때문에). 조건 변수에서 계속 영원히 차단할 수 있습니다.

어떻게 동작해야합니까? 그것은 일어나기를 기다리는 커다란 못생긴 경쟁 조건처럼 보입니다. 조건 변수를 알리고 술어가 true를 반환하면 스레드가 차단을 해제해야합니다. 그러나 우리가 술어를 확인하고 잠을 자지 못하는 사이에 림보 (limbo)에 있다면 우리는 영원히 차단됩니다. std::condition_variable::wait은 원자 함수가 아닙니다.

표준이 그것에 대해 무엇을 말하고 실제로 경쟁 조건입니까?

답변

2

모든 베팅이 해제되도록 계약을 위반했습니다. 참고 : http://en.cppreference.com/w/cpp/thread/condition_variable

TLDR : 뮤텍스를 잡고있는 동안 다른 누군가가 술어를 바꾸는 것은 불가능합니다. 당신은 std::condition_variable::wait를 호출하기 전에 그 뮤텍스를 획득해야한다 (모두 wait 발표하기 때문에 뮤텍스, 그리고 그 계약 때문에) 뮤텍스 을 잡고있는 동안

님의 술어의 기본 변수를 변경하기로되어 있습니다.시나리오에서

당신은 while (!_Pred())는 술어가 보유하지 않는 것을보고 후 변화가 일어난 설명하지만 wait(_Lck) 전에 뮤텍스를 해제 할 수있는 기회를 가졌다. 이것은 뮤텍스를 유지하지 않고 술어가 검사하는 것을 변경했음을 의미합니다. 당신은 규칙을 위반했고 경쟁 조건 또는 무한 대기는 여전히 UB의 최악의 종류는 아닙니다. 적어도 이러한 ... 지역 및 당신이 그렇게 오류를 찾을 수 있습니다 위반 한 규칙과 관련된

당신도, 규칙에 의해 재생하는 경우

:

  1. 웨이터는
  2. 먼저 뮤텍스의 보류 소요
  3. std::condition_variable::wait으로갑니다. (알리미가 여전히 뮤텍스를 기다린다는 것을 상기하십시오.)
  4. 술어를 검사하고 보유하지 않는 것을 봅니다. (도 여전히이 뮤텍스를 기다립니다.)
  5. 뮤텍스를 해제하고 대기하기 위해 구현 정의 된 마술을 호출하면 이제는 알리미가 진행될 수 있습니다.
  6. 알리미가 마침내 뮤텍스를 가져올 수있었습니다.
  7. 노티 파이어는 조건부가 참이되도록 변경해야하는 모든 것을 변경합니다.
  8. 알리미는 std::condition_variable::notify_one을 호출합니다.
또는

:

  1. 신고자가 뮤텍스를 취득한다. (뮤텍스를 얻으려고 할 때 웨이터가 막혔다는 것을 기억하십시오.)
  2. 알리미는 술어가 true를 유지하기 위해 바꿀 필요가있는 부분을 변경합니다. (웨이터는 여전히 차단되어 있습니다.)
  3. 알리미가 뮤텍스를 해제합니다. (웨이터가 std::condition_variable::notify_one로 전화를 걸었을 때 뮤텍스가 해제되면 ...)
  4. 웨이터가 뮤텍스를 획득합니다.
  5. 웨이터는 std::condition_variable::wait으로 전화합니다.
  6. 웨이터가 while (!_Pred())비올라를 확인합니다. 술어가 true입니다.
  7. 웨이터는 wait 내부로 들어 가지 않으므로, std::condition_variable::notify_one을 호출하거나 아직 처리하지 못했는지 여부와 상관없이 관련이 없습니다. 공유 변수가 원자하더라도

    , 올바르게 대기 스레드에 수정을 게시하려면 뮤텍스에 따라 수정해야합니다 : cppreference.com의 요구 사항 뒤에 이론적 근거를의

. 이 조건 변수보다는 (윈도우 CONDITION_VARIABLE의, POSIX pthread_cond_t의 등 포함) std::condition_variables들에 대한 특별한 요구 사항에 대한 일반적인 규칙이 있음을

참고.발신자가 가짜 깨우는 처리하지 않도록 술어 걸리는 wait 과부하 그냥 편리한 기능입니다


기억합니다.

효과 : 표준 (§30.5.1/15) 명시 적으로 과부하가 마이크로 소프트의 구현 while 루프에 해당하는 것을 말한다 등가로 :

while (!pred()) 
    wait(lock); 

은 간단합니까 wait 작품? wait에 전화하기 전후에 술어를 테스트합니까? 큰. 너도 똑같이하고있어. 또는 void std::condition_variable::wait(std::unique_lock<std::mutex>& lock);도 질문하나요?


Windows 중요 섹션과 슬림 리더/라이터 잠금은 사용자 모드 시설이 아닌 커널 오브젝트있는 것은 질문에 무형과 무관하다. 대안 구현이 있습니다. Windows가 원자 적으로 CS/SRWL을 릴리스하고 대기 상태로 들어가는 방법 (Mutexes 및 이벤트를 사용하는 비스타 이전 사용자 모드 구현이 잘못됨)을 관리하는 방법을 알고 싶다면 이는 다른 질문입니다.

+0

아하, 나는 "공유 변수가 원자 적이라 할지라도 ..."을 놓쳤다. 이것은 내가 염두에 두었던 것이다. –

+0

@DavidHaim : 이것은 cppreference가 제공하는 힌트입니다. 표준은 그 어떤 것도 말하지 않습니다. 'std :: condition_variable' (일반적으로 조건 변수와 마찬가지로)은 대기 및 통지와 뮤텍스를 유지하는 관계를 생성합니다. 이런 식으로 뮤텍스를 취득해야하는 타당한 이유가 있다고 가정합니다. 아니. 그 뮤텍스와 함께해야 할 일은 당신의 사업입니다. 조건 변수가 실제로하는 유일한 것은 뮤텍스를 해제하고 대기 상태 * 원자 적으로 입력하는 것입니다. "조건"부분은 전적으로 귀하에게 달려 있습니다. – conio

관련 문제