받아 들인 답변이 오해의 소지가 있다고 생각하기 때문에이 답변을 추가하십시오. 실제로 notify _ * ()를 호출하기 전에 다시 잠금을 해제 할 수 있지만, 모든 경우에 스레드로부터 안전한 코드를 위해 notify_one ()을 어딘가 에서 호출하기 전에 뮤텍스를 잠 가야 합니다.
명확히하기 위해 wait ()가 lk를 잠금 해제하고 잠금이 잠기지 않은 경우 정의되지 않은 동작이되기 때문에 wait (lk)에 들어가기 전에 잠금을 가져와야합니다. 이는 notify_one ()의 경우가 아니지만 wait ()를 입력 하고 해당 호출이 뮤텍스를 잠금 해제 하기 전에 notify _ * ()를 호출하지 않도록해야합니다 . 이는 notify _ * ()를 호출하기 전에 동일한 뮤텍스를 잠 가야만 수행 할 수 있습니다.
예를 들어 다음과 같은 경우를 고려하십시오.
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999)
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0)
return;
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
경고 :이 코드에는 버그가 있습니다.
아이디어는 다음과 같습니다. 스레드는 start ()와 stop ()을 쌍으로 호출하지만 start ()가 true를 반환하는 동안에 만 호출됩니다. 예를 들면 :
if (start())
{
stop();
}
어느 시점에서 하나의 (다른) 스레드는 cancel ()을 호출하고 cancel ()에서 반환 된 후 'Do stuff'에 필요한 객체를 파괴합니다. 그러나 cancel ()은 start ()와 stop () 사이에 스레드가있는 동안에는 반환되지 않아야하며 cancel ()이 첫 번째 줄을 실행하면 start ()는 항상 false를 반환하므로 새 스레드가 'Do 물건 '영역.
제대로 작동합니까?
그 이유는 다음과 같습니다.
1) 어떤 스레드가 start ()의 첫 번째 줄을 성공적으로 실행하면 (따라서 true를 반환) 아직 cancel ()의 첫 번째 줄을 실행 한 스레드가없는 것입니다 (총 스레드 수가 1000보다 훨씬 작다고 가정합니다. 방법).
2) 또한 스레드가 start ()의 첫 번째 줄을 성공적으로 실행했지만 stop ()의 첫 번째 줄은 아직 실행하지 않은 경우 모든 스레드가 cancel ()의 첫 번째 줄을 성공적으로 실행하는 것은 불가능합니다 (단 하나의 스레드 만 cancel ()) 호출 : fetch_sub (1000)에서 반환 된 값은 0보다 큽니다.
3) 스레드가 cancel ()의 첫 번째 줄을 실행하면 start ()의 첫 번째 줄은 항상 false를 반환하고 start ()를 호출하는 스레드는 더 이상 'Do stuff'영역에 들어 가지 않습니다.
4) start () 및 stop ()에 대한 호출 수는 항상 균형을 이루므로 cancel ()의 첫 번째 줄이 성공적으로 실행되지 않은 후 stop ()에 대한 (마지막) 호출이 카운트를 일으키는 순간이 항상 있습니다. -1000에 도달하면 notify_one ()이 호출됩니다. 취소의 첫 번째 줄이 해당 스레드를 통과 한 경우에만 발생할 수 있습니다.
너무 많은 스레드가 start () / stop ()을 호출하여 카운트가 -1000에 도달하지 않고 cancel ()이 반환되지 않는 기아 문제를 제외하고는 "가능성이 낮고 오래 지속되지 않음"으로 받아 들일 수 있습니다. 또 다른 버그가 있습니다.
'Do stuff'영역 안에 하나의 스레드가있을 수 있습니다. stop (); 그 순간 스레드는 fetch_sub (1000)로 값 1을 읽고 통과하는 cancel ()의 첫 번째 줄을 실행합니다. 그러나 뮤텍스를 취하거나 wait (lk)를 호출하기 전에 첫 번째 스레드는 stop ()의 첫 번째 줄을 실행하고 -999를 읽고 cv.notify_one ()을 호출합니다!
그런 다음 notify_one ()에 대한이 호출은 조건 변수에서 wait ()-ing하기 전에 수행됩니다! 그리고 프로그램은 무기한 교착 상태가됩니다.
이런 이유로 wait ()를 호출 할 때까지 notify_one ()을 호출 할 수 없어야합니다 . 조건 변수의 힘은 뮤텍스를 원자 적으로 잠금 해제하고 notify_one () 호출이 발생했는지 확인하고 절전 모드로 전환 할 수 있다는 점에 있습니다. 당신은 그것을 속일 수 있지만, 당신은 어떻게 당신이 false에서 true로 조건을 변경하고 있습니다 변수를 변경 할 때마다 뮤텍스가 잠겨 유지하는 필요성을 유지 여기에 설명 된 것처럼 때문에 경쟁 조건의 notify_one ()를 호출하는 동안 잠겨 있습니다.
그러나이 예에서는 조건이 없습니다. 왜 'count == -1000'조건으로 사용하지 않았습니까? 여기서는 전혀 흥미롭지 않기 때문입니다. -1000에 도달하자마자 새 스레드가 'Do stuff'영역에 들어 가지 않을 것입니다. 게다가 스레드는 여전히 start ()를 호출 할 수 있고 카운트를 증가시킬 것입니다 (-999 및 -998 등).하지만 우리는 그것에 대해 신경 쓰지 않습니다. 중요한 것은 -1000에 도달했기 때문에 'Do stuff'영역에 더 이상 스레드가 없음을 확실히 알 수 있다는 것입니다. 우리는 notify_one ()이 호출되는 경우라고 확신하지만 cancel ()이 뮤텍스를 잠그기 전에 notify_one ()을 호출하지 않는지 확인하는 방법은 무엇입니까? 물론 notify_one () 직전에 cancel_mutex를 잠그는 것은 도움이되지 않습니다.
문제는 우리가 조건을 기다리지 않고 있다는 것을에도 불구하고, 여전히,이다 인 조건, 우리는 뮤텍스를 잠글 필요
1) 해당 조건에 도달하기 전 2) notify_one을 호출하기 전.
따라서 올바른 코드는 다음과 같습니다.
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... 동일한 start () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
물론 이것은 하나의 예일 뿐이지 만 다른 경우는 매우 유사합니다. 당신은 당신이하는 조건 변수를 사용하는 거의 모든 경우에 필요한 그 뮤텍스를 가지고)를 notify_one을 (호출하기 전에 (곧) 잠금, 그렇지 않으면 당신이 대기를 호출하기 전에 ()를 호출하는 것이 가능하다.
이 경우에는 notify_one ()을 호출하기 전에 뮤텍스를 잠금 해제했습니다. 블록, 뮤텍스를 다시 해제하기 전에. 필요한 것보다 약간 느립니다.
이 예제는 조건을 변경하는 라인이 wait ()를 호출하는 동일한 스레드에 의해 실행된다는 점에서 좀 특별했습니다.
보다 일반적인 경우는 한 스레드가 단순히 조건이 참이 될 때까지 기다리고 다른 스레드가 해당 조건과 관련된 변수를 변경하기 전에 잠금을 취하는 경우입니다 (이로 인해 참이 될 수 있음). 이 경우 뮤텍스 는 조건이 참이되기 직전과 직후 에 잠 깁니다 . 따라서이 경우에는 notify _ * ()를 호출하기 전에 뮤텍스를 잠금 해제해도됩니다.
wait morphing
최적화 를 활성화하려면 )이 링크에 설명 된 경험 법칙 :보다 예측 가능한 결과를 위해 스레드가 2 개 이상인 상황에서는 WITH 잠금을 알리는 것이 좋습니다.