2009-09-11 14 views
17

Sto cercando un modo per attendere più variabili di condizione. ie. qualcosa come:in attesa di più variabili condizionali in boost?

boost::condition_variable cond1; 
boost::condition_variable cond2; 

void wait_for_data_to_process() 
{ 
    boost::unique_lock<boost::mutex> lock(mut); 

    wait_any(lock, cond1, cond2); //boost only provides cond1.wait(lock); 

    process_data(); 
} 

È possibile qualcosa del genere con le variabili di condizione. E se non ci sono soluzioni alternative?

Grazie

risposta

11

Non credo che si possa fare qualcosa di simile con boost :: thread. Forse perché le variabili di condizione POSIX non consentono questo tipo di costrutto. Naturalmente, Windows ha WaitForMultipleObjects come aJ postato, che potrebbe essere una soluzione se sei disposto a limitare il tuo codice alle primitive di sincronizzazione di Windows.

Un'altra opzione dovrebbe utilizzare un numero inferiore di variabili di condizione: è sufficiente disporre di una variabile di condizione che si attiva quando si verifica qualcosa di "interessante". Poi, ogni volta che vuoi aspettare, esegui un ciclo che controlla se la tua particolare situazione di interesse si è manifestata e, in caso contrario, torna ad aspettare sulla variabile condition. Si dovrebbe essere in attesa su quelle variabili di condizione in un tale ciclo in ogni modo, come attese variabili condizioni sono soggetti a wakeups spurie (da docs boost :: thread, sottolineatura mia):

void wait(boost::unique_lock<boost::mutex>& lock)
...
Effetti:
Chiamare atomicamente lock.unlock() e blocca il thread corrente. Il thread verrà sbloccato quando viene notificato da una chiamata a this->notify_one() o this->notify_all() o spuriamente. ...

+4

Mi sembra che avere una terza variabile di condizione per "1 o 2 cambiato" sia davvero l'approccio migliore. Un altro approccio è quello di convertire l'attesa su condizione in attesa sui descrittori di file utilizzando select e utilizzare pipe per la comunicazione thread. Poiché questa domanda è generica (non spiega quali sono le cose che sono state attese), non è chiaro se la selezione sarà migliore. –

+0

Il problema con l'utilizzo di una variabile di condizione è l'incapsulamento. Non puoi avere funzioni come 'GoStyleChannel :: receive_but_break_if (condition_variable & cv, function & pred)'. In altre parole, supponiamo di avere una funzione di blocco che utilizza già una variabile di condizione. Vuoi che blocchi fino a quando non si verifica la condizione * o * che si verifica la tua condizione. Non puoi farlo senza avere un globale 'std :: condition_variable somethingHasHappenedSomewhere', che è davvero una schifezza. – Timmmm

0
alternative solutions? 

non sono sicuro della libreria Boost, ma è possibile utilizzare WaitForMultipleObjects funzione di aspettare per più oggetti del kernel. Basta controllare se questo aiuta.

+0

Sì, so di WaitForMultipleObject. Sto cercando l'equivalente in boost. Ma sembra che Boost non ne fornisca uno. Vedi: http://lists.boost.org/Archives/boost/2004/12/77175.php –

0

Come evidenzia Managu utilizzando più condizioni potrebbe non essere una buona soluzione in primo luogo. Quello che vuoi fare dovrebbe essere possibile implementarlo usando i semafori.

11

Come già risposto a Managu, è possibile utilizzare la stessa variabile di condizione e controllare più "eventi" (variabili bool) nel proprio ciclo while. Tuttavia, l'accesso concorrente a queste variabili bool deve essere protetto utilizzando lo stesso mutex utilizzato da condvar.

Dal momento che ho già passato attraverso la fatica di digitare questo esempio di codice per un relativo question, io ripubblicare qui:

boost::condition_variable condvar; 
boost::mutex mutex; 
bool finished1 = false; 
bool finished2 = false; 

void longComputation1() 
{ 
    { 
     boost::lock_guard<boost::mutex> lock(mutex); 
     finished1 = false; 
    } 
    // Perform long computation 
    { 
     boost::lock_guard<boost::mutex> lock(mutex); 
     finished1 = true; 
    } 
    condvar.notify_one(); 
} 

void longComputation2() 
{ 
    { 
     boost::lock_guard<boost::mutex> lock(mutex); 
     finished2 = false; 
    } 
    // Perform long computation 
    { 
     boost::lock_guard<boost::mutex> lock(mutex); 
     finished2 = true; 
    } 
    condvar.notify_one(); 
} 

void somefunction() 
{ 
    // Wait for long computations to finish without "spinning" 
    boost::lock_guard<boost::mutex> lock(mutex); 
    while(!finished1 && !finished2) 
    { 
     condvar.wait(lock); 
    } 

    // Computations are finished 
} 
+0

+1 questa è una buona soluzione – Alexey

0

Utilizzando la stessa variabile di condizione per più eventi funziona tecnicamente, ma doesn' t consentire l'incapsulamento. Quindi ho avuto un tentativo di creare una classe che lo supporta. Non ancora testato! Inoltre non supporta notify_one() in quanto non ho ancora capito come implementarlo.

#pragma once 

#include <condition_variable> 
#include <unordered_set> 

// This is like a `condition_variable` but you can wait on multiple `multi_condition_variable`s. 
// Internally it works by creating a new `condition_variable` for each `wait_any()` and registering 
// it with the target `multi_condition_variable`s. When `notify_all()` is called, the main `condition_variable` 
// is notified, as well as all the temporary `condition_variable`s created by `wait_any()`. 
// 
// There are two caveats: 
// 
// 1. You can't call the destructor if any threads are `wait()`ing. This is difficult to get around but 
//  it is the same as `std::wait_condition` anyway. 
// 
// 2. There is no `notify_one()`. You can *almost* implement this, but the only way I could think to do 
//  it was to add an `atomic_int` that indicates the number of waits(). Unfortunately there is no way 
//  to atomically increment it, and then wait. 
class multi_condition_variable 
{ 
public: 
    multi_condition_variable() 
    { 
    } 

    // Note that it is only safe to invoke the destructor if no thread is waiting on this condition variable. 
    ~multi_condition_variable() 
    { 
    } 

    // Notify all threads calling wait(), and all wait_any()'s that contain this instance. 
    void notify_all() 
    { 
     _condition.notify_all(); 
     for (auto o : _others) 
      o->notify_all(); 
    } 

    // Wait for notify_all to be called, or a spurious wake-up. 
    void wait(std::unique_lock<std::mutex>& loc) 
    { 
     _condition.wait(loc); 
    } 

    // Wait for any of the notify_all()'s in `cvs` to be called, or a spurious wakeup. 
    static void wait_any(std::unique_lock<std::mutex>& loc, std::vector<std::reference_wrapper<multi_condition_variable>> cvs) 
    { 
     std::condition_variable c; 
     for (multi_condition_variable& cv : cvs) 
      cv.addOther(&c); 
     c.wait(loc); 
     for (multi_condition_variable& cv : cvs) 
      cv.removeOther(&c); 
    } 

private: 
    void addOther(std::condition_variable* cv) 
    { 
     std::lock_guard<std::mutex> lock(_othersMutex); 
     _others.insert(cv); 
    } 

    void removeOther(std::condition_variable* cv) 
    { 
     // Note that *this may have been destroyed at this point. 
     std::lock_guard<std::mutex> lock(_othersMutex); 
     _others.erase(cv); 
    } 

    // The condition variable. 
    std::condition_variable _condition; 

    // When notified, also notify these. 
    std::unordered_set<std::condition_variable*> _others; 

    // Mutex to protect access to _others. 
    std::mutex _othersMutex; 
}; 

// Example use: 
// 
// multi_condition_variable cond1; 
// multi_condition_variable cond2; 
// 
// void wait_for_data_to_process() 
// { 
//  unique_lock<boost::mutex> lock(mut); 
// 
//  multi_condition_variable::wait_any(lock, {cond1, cond2}); 
// 
//  process_data(); 
// }