Zoeken…


Invoering

Voorwaardelijke variabelen zijn nuttig in gevallen waarin u wilt dat een thread wacht op iets dat in een andere thread gebeurt. In een producent / consument-scenario met een of meer producerende threads en een consumerende thread kunnen bijvoorbeeld voorwaardelijke variabelen worden gebruikt om de consumerende thread te signaleren dat er nieuwe gegevens beschikbaar zijn.

Opmerkingen

Algemeen proces

Een wachttijd op een voorwaardelijke variabele (queueCond in het voorbeeld van de producent / consument) is altijd gekoppeld aan een mutex (de wachtrijMutex in het voorbeeld van de producent / consument) en moet ook altijd worden gekoppeld aan een "normale" statusvariabele (queue.empty ( ) in het voorbeeld van producent / consument). Bij correct gebruik zorgt dit ervoor dat er geen nieuwe gegevens worden gemist bij de consument.

Over het algemeen moet het proces zijn:

  • Voor de signaaldraad:
    1. Vergrendel de mutex
    2. Werk alle gegevens en statusvariabelen bij
    3. Geef de voorwaardevariabele aan
    4. Ontgrendel de mutex
  • Voor de wachtende thread:
    1. Vergrendel de mutex
    2. Voer een while lus uit op de statusvariabele, looping zolang de gegevens niet gereed zijn
    3. Wacht in de while lus op de voorwaardevariabele met pthread_cond_wait()
    4. Wanneer de while lus wordt afgesloten, weten we nu zeker dat nieuwe gegevens gereed zijn en dat de mutex is vergrendeld
    5. Doe iets met de gegevens
    6. Ontgrendel de mutex en herhaal

Met dit schema, ongeacht wanneer de signalerings- en wachtende threads zijn gepland, zal de wachtende thread nooit gegevens missen (zoals in, het zal nooit voor altijd blijven hangen met geldige data gereed). Dit kan worden gerealiseerd door handmatig de stappen voor de signaleringsdraad te proberen te doorlopen, de statussen van de mutex-, conditie- en statusvariabelen op te nemen voor elk van de stappen in de wachtende thread.

pthread_cond_wait en de mutex

Om het bovenstaande proces te vergemakkelijken, is het vereist om pthread_cond_wait() aan te roepen met de mutex vergrendeld. Wanneer pthread_cond_wait() wordt aangeroepen, wordt de mutex ontgrendeld voordat de thread in de slaapstand wordt gezet en net voordat hij om welke reden dan ook terugkeert, wordt de mutex opnieuw vergrendeld. Dit betekent ook dat als een andere thread momenteel de mutex heeft vergrendeld, pthread_cond_wait() wacht tot de mutex wordt ontgrendeld, en totdat de wachtende thread de mutex daadwerkelijk kan verwerven - hij zal er samen met andere threads proberen te vergrendelen de mutex tegelijkertijd.

Valse wake-ups

Ook kan het lijken alsof de while lus die op de statusvariabele wacht, kan worden vervangen door een eenvoudige if instructie. De while lus is echter nodig, omdat de Posix-standaard pthread_cond_wait pthread_cond_wait() toestaat zogenaamde "valse" wake-ups te doen tijdens het wachten, zonder daadwerkelijk te worden gesignaleerd. Daarom moet de code de statusvariabele opnieuw controleren om te zien of pthread_cond_wait() geretourneerd omdat het daadwerkelijk is gesignaleerd of vanwege een van deze valse wake-ups.

Producent / consument voorbeeld

pthread_mutex_t queueMutex;
pthread_cond_t queueCond;
Queue queue;

void Initialize() {
    //Initialize the mutex and the condition variable
    pthread_mutex_init(&queueMutex, NULL);
    pthread_cond_init(&queueCond, NULL);
}

void Producer() {
    //First we get some new data
    Data *newData = MakeNewData();

    //Lock the queue mutex to make sure that adding data to the queue happens correctly
    pthread_mutex_lock(&queueMutex);

    //Push new data to the queue
    queue.push(newData);

    //Signal the condition variable that new data is available in the queue
    pthread_cond_signal(&queueCond);

    //Done, unlock the mutex
    pthread_mutex_unlock(&queueMutex);
}


void Consumer() {

    //Run the consumer loop
    while(1) {

        //Start by locking the queue mutex
        pthread_mutex_lock(&queueMutex);

        //As long as the queue is empty,
        while(queue.empty()) {
            // - wait for the condition variable to be signalled
            //Note: This call unlocks the mutex when called and
            //relocks it before returning!
            pthread_cond_wait(&queueCond, &queueMutex);
        }

        //As we returned from the call, there must be new data in the queue - get it,
        Data *newData = queue.front();
        // - and remove it from the queue
        queue.pop();

        //Now unlock the mutex
        pthread_mutex_unlock(&queueMutex);
        
        // - and process the new data
        ProcessData(newData);
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow