pthreads
Variabili condizionali
Ricerca…
introduzione
Le variabili condizionali sono utili nei casi in cui si desidera che un thread attenda qualcosa che si verifica in un altro thread. Ad esempio, in uno scenario produttore / consumatore con uno o più thread di produzione e un thread di consumo, è possibile utilizzare variabili condizionali per segnalare al thread di consumo che sono disponibili nuovi dati.
Osservazioni
Processo generale
Un'attesa su una variabile condizionale (queueCond nell'esempio produttore / consumatore) è sempre accoppiata a un mutex (il queueMutex nell'esempio produttore / consumatore), e dovrebbe sempre essere accoppiata anche a una variabile di stato "normale" (queue.empty ( ) nell'esempio produttore / consumatore). Se usato correttamente, garantisce che nessun nuovo dato venga perso nel consumatore.
In generale, il processo dovrebbe essere:
- Per il thread di segnalazione:
- Blocca il mutex
- Aggiorna eventuali dati e variabili di stato
- Segnala la variabile di condizione
- Sblocca il mutex
- Per il thread in attesa:
- Blocca il mutex
- Esegui un ciclo
whilesulla variabile di stato, eseguendo il looping finché i dati non sono pronti - Nel ciclo
while, attendi sulla variabile condition conpthread_cond_wait() - Quando il ciclo
whiletermina, ora siamo sicuri che i nuovi dati sono pronti e che il mutex è bloccato - Fai qualcosa con i dati
- Sblocca il mutex e ripeti
Con questo schema, non importa quando sono programmati i thread di segnalazione e di attesa, il thread in attesa non perderà mai i dati (come in, non sarà mai bloccato in attesa per sempre con dati validi pronti). Ciò può essere realizzato tentando manualmente di eseguire i passaggi per il thread di segnalazione, registrando gli stati del mutex, le condizioni e le variabili di stato, per ciascuno dei passaggi nel thread in attesa.
pthread_cond_wait e il mutex
Per facilitare il processo sopra, è necessario chiamare pthread_cond_wait() con il mutex bloccato. Quando viene chiamato, pthread_cond_wait() sbloccherà il mutex prima di mettere il thread in stop, e, appena prima di tornare per qualsiasi motivo, il mutex verrà ricollocato. Ciò significa anche che se qualche altro thread ha attualmente il mutex bloccato, pthread_cond_wait() attenderà il mutex per essere sbloccato, e finché il thread in attesa non potrà effettivamente acquisire il mutex, lo contenderà insieme a qualsiasi altro thread che tenta di bloccare il mutex allo stesso tempo.
Sveglie spurie
Inoltre, può sembrare che il ciclo while attesa sulla variabile di stato possa essere sostituito da un'istruzione if semplice. Tuttavia, il ciclo while è necessario, in quanto lo standard Posix consente a pthread_cond_wait() di effettuare i cosiddetti wakeup "spuri" durante l'attesa, senza essere effettivamente segnalati. Pertanto, il codice deve ricontrollare la variabile di stato per vedere se pthread_cond_wait() restituito a causa della pthread_cond_wait() effettiva o a causa di uno di questi spuri wakeup.
Esempio di produttore / consumatore
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);
}
}