답변:
조건 변수가 (또는 원래) 구현 된 방식 일뿐입니다.
뮤텍스는 조건 변수 자체 를 보호 하는 데 사용 됩니다 . 그래서 기다리기 전에 잠 가야하는 이유입니다.
대기는 뮤텍스를 "원자 적으로"잠금 해제하여 다른 사람들이 조건 신호에 액세스 할 수있게합니다 (신호 발생). 그런 다음 조건 변수에 신호를 보내거나 브로드 캐스트하면 대기 목록의 스레드 중 하나 이상이 깨어나고 해당 스레드에 대해 뮤텍스가 다시 마술처럼 고정됩니다.
일반적으로 조건 변수와 함께 다음 작업이 작동 방식을 보여줍니다. 다음 예제는 조건 변수에 대한 신호를 통해 작업이 제공되는 작업자 스레드입니다.
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
do the work.
unlock mutex.
clean up.
exit thread.
대기가 리턴 될 때 사용 가능한 것이 있으면이 루프 내에서 작업이 수행됩니다. 스레드가 작업을 중지하도록 플래그가 지정되면 (보통 다른 스레드가 종료 조건을 설정 한 후 조건 변수를 시작하여 스레드를 깨우기 위해) 루프가 종료되고 뮤텍스가 잠금 해제되고이 스레드가 종료됩니다.
위의 코드는 작업이 완료되는 동안 뮤텍스가 잠긴 상태에서 단일 소비자 모델입니다. 멀티 소비자의 변화를 들어, 당신은 같이 사용할 수 있습니다 예 :
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
copy work to thread local storage.
unlock mutex.
do the work.
lock mutex.
unlock mutex.
clean up.
exit thread.
이것은 다른 소비자가 일을하는 동안 일을받을 수있게합니다.
조건 변수는 어떤 상황을 폴링해야하는 부담을 덜어 주지만 대신 어떤 상황이 발생할 때 다른 스레드가 사용자에게 알려줄 수 있습니다. 다른 스레드는 다음과 같이 스레드가 작동 함을 알 수 있습니다.
lock mutex.
flag work as available.
signal condition variable.
unlock mutex.
종종 잘못된 pthread_cond_wait
모닝콜 이라고 잘못 부르는 것의 대다수는 일반적으로 여러 스레드가 호출 (브로드 캐스트) 내에서 신호를 보냈기 때문에 뮤텍스와 함께 돌아와서 작업을 수행 한 다음 다시 대기하기 때문입니다.
그런 다음 수행 할 작업이 없을 때 두 번째 신호 스레드가 나올 수 있습니다. 따라서 작업을 수행해야 함을 나타내는 추가 변수가 있어야했습니다 (이것은 본질적으로 condvar / mutex 쌍으로 뮤텍스로 보호되었습니다-그러나 다른 스레드는 뮤텍스를 변경하기 전에 뮤텍스를 잠그는 데 필요했습니다).
그것은 이었다 쓰레드가 다른 프로세스에 의해 쫓겨되지 않고 상태 대기에서 반환하는 pthreads의 작업을 모두 개발 / 코드의 서비스와 사용자로 내 여러 해에, (이 진짜 가짜 웨이크 업입니다)하지만, 기술적으로 가능 그들 중 한 번도이 중 하나를받지 못했습니다. 어쩌면 HP가 적절한 구현을했기 때문일 수도 있습니다 :-)
어쨌든, 잘못된 케이스를 처리 한 동일한 코드도 작업 가능한 플래그가 설정되지 않았기 때문에 진짜 가짜 웨이크 업도 처리했습니다.
do something
안에 while
있습니까?
조건 만 신호 할 수있는 경우 조건 변수는 상당히 제한적입니다. 일반적으로 신호가 발생한 조건과 관련된 일부 데이터를 처리해야합니다. 경쟁 조건을 도입하지 않고 지나치게 복잡해 지려면 신호 처리 / 웨이크 업을 원자 적으로 수행해야합니다.
pthreads는 기술적 인 이유로 가짜 웨이크 업을 제공 할 수도 있습니다 . 즉, 술어를 확인해야하므로 조건이 실제로 신호되었는지 확인하고 가짜 웨이크 업과 구별 할 수 있습니다. 대기 상태와 관련하여 이러한 조건을 확인하려면 보호해야합니다. 따라서 조건 변수는 해당 조건을 보호하는 뮤텍스를 잠 그거나 잠금 해제하는 동안 원자 적으로 대기 / 깨우는 방법이 필요합니다.
일부 데이터가 생성되었다는 알림을받는 간단한 예를 고려하십시오. 다른 스레드가 원하는 데이터를 만들고 해당 데이터에 대한 포인터를 설정했을 수 있습니다.
'some_data'포인터를 통해 일부 소비자 데이터를 다른 소비자 스레드에 제공하는 생산자 스레드를 상상해보십시오.
while(1) {
pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
char *data = some_data;
some_data = NULL;
handle(data);
}
당신은 자연스럽게 많은 경쟁 조건 some_data = new_data
을 갖게 될 것입니다. 만약 당신이 깨어 난 직후에 다른 스레드가 그랬다면 어떻게 합니까?data = some_data
이 경우를 지키기 위해 자신의 뮤텍스를 실제로 만들 수는 없습니다.
while(1) {
pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
pthread_mutex_lock(&mutex);
char *data = some_data;
some_data = NULL;
pthread_mutex_unlock(&mutex);
handle(data);
}
작동하지 않지만 뮤텍스를 깨우고 잡는 사이에 여전히 경쟁 조건이 있습니다. pthread_cond_wait 앞에 뮤텍스를 배치해도 도움이되지 않습니다. 대기하는 동안 뮤텍스를 잡아야합니다. 즉, 생산자는 절대 뮤텍스를 잡을 수 없습니다. (이 경우 생산자에게 완료되었음을 알리는 두 번째 조건 변수를 작성할 수 있습니다. some_data
특히 생산자 / 소비자가 많은 경우 복잡해집니다.)
따라서 조건에서 대기 / 깨어날 때 뮤텍스를 원자 적으로 해제 / 잡을 수있는 방법이 필요합니다. 이것이 pthread 조건 변수가하는 일이며, 여기 당신이하는 일이 있습니다 :
while(1) {
pthread_mutex_lock(&mutex);
while(some_data == NULL) { // predicate to acccount for spurious wakeups,would also
// make it robust if there were several consumers
pthread_cond_wait(&cond,&mutex); //atomically lock/unlock mutex
}
char *data = some_data;
some_data = NULL;
pthread_mutex_unlock(&mutex);
handle(data);
}
(생산자는 자연스럽게 동일한 예방 조치를 취해야하며 항상 같은 뮤텍스로 'some_data'를 지키고 some_data가 현재! = NULL 인 경우 some_data를 덮어 쓰지 않아야합니다)
while (some_data != NULL)
조건 변수를 한 번 이상 기다리도록 do-while 루프가 아니어야합니까 ?
while(some_data != NULL)
할 while(some_data == NULL)
?
POSIX 조건 변수는 상태 비 저장입니다. 따라서 국가를 유지하는 것은 귀하의 책임입니다. 대기하는 스레드와 다른 스레드에게 대기를 중지하도록 지시하는 스레드가 상태에 액세스하므로 뮤텍스에 의해 보호되어야합니다. 뮤텍스없이 조건 변수를 사용할 수 있다고 생각하면 조건 변수가 상태가 없다는 것을 파악하지 못했습니다.
조건 변수는 조건 주위에 구축됩니다. 조건 변수를 기다리는 스레드가 일부 조건을 기다리고 있습니다. 조건 변수를 신호하는 스레드가 해당 조건을 변경합니다. 예를 들어, 스레드가 일부 데이터가 도착하기를 기다리는 중일 수 있습니다. 다른 스레드는 데이터가 도착했음을 알 수 있습니다. "데이터가 도착했습니다"가 조건입니다.
다음은 단순화 된 조건 변수의 일반적인 사용법입니다.
while(1)
{
pthread_mutex_lock(&work_mutex);
while (work_queue_empty()) // wait for work
pthread_cond_wait(&work_cv, &work_mutex);
work = get_work_from_queue(); // get work
pthread_mutex_unlock(&work_mutex);
do_work(work); // do that work
}
스레드가 작업을 기다리는 방법을 참조하십시오. 작품은 뮤텍스에 의해 보호됩니다. 대기는 뮤텍스를 해제하여 다른 스레드가이 스레드에 약간의 작업을 제공 할 수 있도록합니다. 신호를받는 방법은 다음과 같습니다.
void AssignWork(WorkItem work)
{
pthread_mutex_lock(&work_mutex);
add_work_to_queue(work); // put work item on queue
pthread_cond_signal(&work_cv); // wake worker thread
pthread_mutex_unlock(&work_mutex);
}
공지 사항 당신은 필요한 작업 큐를 보호하기 위해 뮤텍스를. 조건 변수 자체에는 작동 여부가 없습니다. 즉, 조건 변수 는 조건 과 연결 되어야 하며 해당 조건은 코드에서 유지 관리해야하며 스레드간에 공유되므로 뮤텍스에 의해 보호되어야합니다.
모든 조건 변수 함수에 뮤텍스가 필요한 것은 아닙니다. 대기 작업 만 가능합니다. 신호 및 방송 작업에는 뮤텍스가 필요하지 않습니다. 조건 변수는 특정 뮤텍스와 영구적으로 연관되지 않습니다. 외부 뮤텍스는 조건 변수를 보호하지 않습니다. 조건 변수에 대기중인 스레드 큐와 같은 내부 상태가있는 경우 조건 변수 내부의 잠금으로 보호해야합니다.
대기 작업은 다음과 같은 이유로 조건 변수와 뮤텍스를 결합합니다.
이러한 이유로 대기 작업은 뮤텍스와 조건을 모두 인수로 사용하여 뮤텍스 소유에서 대기로 스레드의 원자 전송을 관리 할 수 있으므로 스레드가 웨이크 업 경쟁 조건에 희생되지 않습니다 .
스레드가 뮤텍스를 포기한 다음 상태 비 저장 동기화 객체를 대기하지만 원자 적이 지 않은 방식으로 대기하는 경우 웨이크 업 경쟁 조건이 발생합니다. 스레드에 더 이상 잠금이없는 시간 창이 있습니다. 아직 객체를 기다리지 않았습니다. 이 창에서 다른 스레드가 들어 와서 대기 상태를 true로 만들고 상태 비 저장 동기화 신호를 보낸 다음 사라질 수 있습니다. 상태 비 저장 개체는 신호가 있다는 것을 기억하지 못합니다 (상태 비 저장). 따라서 원래 스레드는 상태 비 저장 동기화 개체에서 절전 모드로 전환되고 필요한 조건이 이미 충족 되었더라도 깨어나지 않습니다.
조건 변수 대기 함수는 호출 스레드가 뮤텍스를 포기하기 전에 웨이크 업을 안정적으로 포착하도록 등록되어 웨이크 업 손실을 피합니다. 조건 변수 대기 함수가 뮤텍스를 인수로 사용하지 않으면 불가능합니다.
나는 이 페이지 만큼 간결하고 읽기 쉬운 다른 답변을 찾지 못했다 . 일반적으로 대기 코드는 다음과 같습니다.
mutex.lock()
while(!check())
condition.wait()
mutex.unlock()
wait()
뮤텍스 를 감싸는 데는 세 가지 이유가 있습니다 .
signal()
전에 할 수 있으며이 wait()
깨우기가 빠질 것입니다.check()
다른 스레드의 수정에 의존하므로 어쨌든 상호 제외가 필요합니다.세 번째 요점이 항상 우려되는 것은 아닙니다. 역사적 맥락이 기사 와이 대화에 연결되어 있습니다 .
이 메커니즘과 관련하여 가짜 웨이크 업이 종종 언급됩니다 (즉, 대기 스레드가 signal()
호출 되지 않고 해제 됨 ). 그러나 이러한 이벤트는 looped에 의해 처리됩니다 check()
.
조건 변수는 피하도록 설계된 경쟁을 피할 수있는 유일한 방법이기 때문에 뮤텍스와 연관됩니다.
// incorrect usage:
// thread 1:
while (notDone) {
pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable
pthread_mutex_unlock(&mutex);
if (ready) {
doWork();
} else {
pthread_cond_wait(&cond1); // invalid syntax: this SHOULD have a mutex
}
}
// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
protectedReadyToRuNVariable = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond1);
Now, lets look at a particularly nasty interleaving of these operations
pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
protectedReadyToRuNVariable = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond1);
if (ready) {
pthread_cond_wait(&cond1); // uh o!
이 시점에서 조건 변수를 알리는 스레드가 없으므로 protectedReadyToRunVariable이 갈 준비가 되었음에도 불구하고 thread1은 영원히 대기합니다!
조건 변수가이 주위에있는 유일한 방법은 원자 적으로 동시에 조건 변수에 대기하는 동안 시작 뮤텍스를 해제. 이것이 cond_wait 함수에 뮤텍스가 필요한 이유입니다
// correct usage:
// thread 1:
while (notDone) {
pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable
if (ready) {
pthread_mutex_unlock(&mutex);
doWork();
} else {
pthread_cond_wait(&mutex, &cond1);
}
}
// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
protectedReadyToRuNVariable = true;
pthread_cond_signal(&mutex, &cond1);
pthread_mutex_unlock(&mutex);
뮤텍스는 호출 할 때 잠겨 있어야합니다 pthread_cond_wait
. 그것을 호출하면 원자 적으로 모두 뮤텍스를 잠금 해제 한 다음 조건을 차단합니다. 조건이 신호되면 원자 적으로 다시 잠그고 돌아갑니다.
이는 시그널링을 수행하는 스레드가 뮤텍스가 해제 될 때까지 기다렸다가 그 상태를 시그널링 할 수 있다는 점에서 원하는 경우 예측 가능한 스케줄링의 구현을 허용한다.
조건 변수의 실제 예를 원한다면 클래스에서 연습을했습니다.
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "unistd.h"
int compteur = 0;
pthread_cond_t varCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex_compteur;
void attenteSeuil(arg)
{
pthread_mutex_lock(&mutex_compteur);
while(compteur < 10)
{
printf("Compteur : %d<10 so i am waiting...\n", compteur);
pthread_cond_wait(&varCond, &mutex_compteur);
}
printf("I waited nicely and now the compteur = %d\n", compteur);
pthread_mutex_unlock(&mutex_compteur);
pthread_exit(NULL);
}
void incrementCompteur(arg)
{
while(1)
{
pthread_mutex_lock(&mutex_compteur);
if(compteur == 10)
{
printf("Compteur = 10\n");
pthread_cond_signal(&varCond);
pthread_mutex_unlock(&mutex_compteur);
pthread_exit(NULL);
}
else
{
printf("Compteur ++\n");
compteur++;
}
pthread_mutex_unlock(&mutex_compteur);
}
}
int main(int argc, char const *argv[])
{
int i;
pthread_t threads[2];
pthread_mutex_init(&mutex_compteur, NULL);
pthread_create(&threads[0], NULL, incrementCompteur, NULL);
pthread_create(&threads[1], NULL, attenteSeuil, NULL);
pthread_exit(NULL);
}
그것은 개념적 필요보다는 특정한 디자인 결정 인 것으로 보인다.
pthreads 문서에 따라 뮤텍스가 분리되지 않은 이유는 뮤텍스를 결합하여 성능이 크게 향상되어 뮤텍스를 사용하지 않으면 일반적인 경쟁 조건으로 인해 거의 항상 수행 될 것으로 기대하기 때문입니다.
https://linux.die.net/man/3/pthread_cond_wait
뮤텍스 및 조건 변수의 특징
뮤텍스 획득 및 릴리스는 조건 대기에서 분리되도록 제안되었습니다. 실제로 실시간 구현을 용이하게하는 작업의 결합 된 특성이기 때문에 거부되었습니다. 이러한 구현은 호출자에게 투명한 방식으로 조건 변수와 뮤텍스 사이에서 우선 순위가 높은 스레드를 원자 적으로 이동할 수 있습니다. 이를 통해 추가 컨텍스트 전환을 방지하고 대기 스레드에 신호를 보낼 때보다 결정적인 뮤텍스를 얻을 수 있습니다. 따라서 공정성 및 우선 순위 문제는 스케줄링 원칙에 의해 직접 처리 될 수 있습니다. 또한 현재 조건 대기 작업이 기존 사례와 일치합니다.
그것에 대해 많은 주석이 있지만 다음 예제를 통해 설명하고 싶습니다.
1 void thr_child() {
2 done = 1;
3 pthread_cond_signal(&c);
4 }
5 void thr_parent() {
6 if (done == 0)
7 pthread_cond_wait(&c);
8 }
코드 스 니펫에 어떤 문제가 있습니까? 계속 진행하기 전에 어느 정도 숙고하십시오.
이 문제는 정말 미묘합니다. 부모가를 호출
thr_parent()
한 다음 그 값을 검사하면 그 값 done
이 확인 0
되어 잠자기 상태가됩니다. 그러나 전화가 잠들기 위해 대기하기 직전에 부모는 6-7 줄 사이에 중단되고 아이는 달려갑니다. 자식은 상태 변수 done
를 1
및 신호로 변경
하지만 대기중인 스레드가 없으므로 스레드가 깨어나지 않습니다. 부모가 다시 달리면 영원히 잠이 듭니다.
잠금을 개별적으로 획득 한 상태에서 수행하면 어떻게됩니까?