2016-01-13 32 views
12

Sto testando uno scenario produttore-consumatore in cui il produttore blocca quando tenta di scrivere su una coda che è piena. Voglio testare che il thread del produttore si riattiva correttamente e funziona come da aspettativa dopo che il consumatore ha letto dalla coda completa *. L'API di scrittura in coda chiama pthread_cond_wait() sul rilevamento della coda completa e l'API di lettura segnala la variabile condizionale dopo aver letto dalla coda.Come programmare un pthread_cond_signal() in modo tale che segua sempre un pthread_cond_wait() su un altro thread?

Come si assicura l'occorrenza di Sequenza 3 su qualsiasi altra sequenza di operazioni nel mio ambiente di test?

enter image description here

* Sì voglio testare questo scenario limitata a parte; ci sono altri test che testano la funzionalità dell'intera coda, questo test è in aggiunta a quelli.

Maggiori informazioni -
c'è un singolo mutex governa la coda. Lì sono 2 variabili condizionali - una per segnalare la scrittura (tutte le scritture), una per leggere il segnale (tutte le letture). I blocchi API queue_write nella lettura condvar se la coda è piena. I blocchi API queue_read su scrivono condvar se la coda è vuota. Tutta la segnalazione avviene sotto l'egida del mutex.
C'è molta più sfumatura in coda ma ai fini dell'impostazione del contesto per questa domanda questo è un riepilogo adeguato del funzionamento della coda .

+0

Avviare solo il thread del produttore, attendere fino a quando non viene bloccato, quindi avviare l'utente? – EOF

+0

Esistono funzioni API che consentono di testare quando la coda è piena? – Superlokkus

+0

@EOF - Come faccio a sapere a livello di codice che il thread del produttore è ora bloccato? (Sto cercando qualcosa oltre "* aspetta un adeguato periodo di tempo *") –

risposta

1

cura (Si tenga presente che la gestione degli errori delle chiamate pthread sono stati omessi)

È possibile raggiungere questo testando se la coda è piena, con la funzione di lei ha citato nei commenti. Per questa risposta presumo che sia bool is_queue_full(const queue*).

Nel tuo caso di test puoi garantire lo scenario 3, creando il produttore e creando un consumatore, se e solo se, dopo che la coda è piena. Come bool is_queue_full (coda *); // non devono utilizzare il mutex in sé, forse segnare solo per uso interno

struct queue { 
    /* Actual queue stuff */ 
    pthread_mutex_t queue_mutex; 
    pthread_cond_t read_condvar; 
    pthread_cond_t write_condvar; 
}; 

void wait_until_queue_is_full (queue *q) { 

    pthread_mutex_lock(&q->queue_mutex); 
    while (!is_queue_full(q)){ //Use in loop because of Spurious wakeups 
     pthread_cond_wait(&q->write_condvar,&q->queue_mutex); 
    } 
    pthread_mutex_unlock(&q->queue_mutex); 
} 

bool test_writer_woke_up(queue *q); 

bool test_case(){ 
    queue *q = create_queue(); 
    producer *p = create_producer(q); 

    wait_until_queue_is_full(q); 

    return test_writer_woke_up(q); //or cache the result and destroy your queue, but if your testrunner process will quit anyway... 
} 

wait_until_queue_is_full sarà solo verificare se la coda è piena, e se no, si aspetta, come ogni lettore, fino a quando il produttore aka produttore ha reso completo.Quindi la tua testcase può produrre i consumatori con qualcosa come test_writer_woke_up void intern_consume_stuff (coda q);/ La funzione stagista che prende roba dalla coda, ma doesen't a cuore la sychronization aka mutex e condvar */

bool test_writer_woke_up(queue *q){ 
    pthread_mutex_lock(&q->queue_mutex); //Could be omitted in this testcase (together with the 1 unlock below of course) 
    void intern_consume_stuff(queue *q); 
    pthread_mutex_unlock(&q->queue_mutex); //Could be omitted in this testcase (together with the 1 lock above of course) 
    pthread_cond_signal(&q->read_condvar); 

    /* Adjust these as you like to give your producer/writer time to wake up and produce something 
    */ 
    unsigned retry_count = 5; 
    unsigned sleep_time = 1; 

    //timed cond wait approach 
    for (; retry_count > 0; --retry_count){ 
     pthread_mutex_lock(&q->queue_mutex); 
     struct timespec ts; 
     clock_gettime(CLOCK_REALTIME, &ts); 
     ts.tv_sec += sleep_time; 
     int timed_cond_rc = 0; 
     while (!is_queue_full(q) && timed_cond_rc == 0) { 
      timed_cond_rc = pthread_cond_timedwait(&q->write_condvar, &q->queue_mutex, &ts); 
     } 
     if (is_queue_full(q)) { 
      pthread_mutex_unlock(&q->queue_mutex); 
      return true; 
     } 
     assert(timed_cond_rc == ETIMEDOUT); 
     continue; 
    } 
    return false; 
} 

Se hanno utilizzato il tempo assoluto di attesa, perché si doveva ricalcolare tempi relativi, o per semplificare le cose si potrebbe sostituire il ciclo for con questo approccio ingenuo

//naive busy approach 
for (; retry_count > 0; --retry_count){ 
    pthread_mutex_lock(q->queue_mutex); 
    const bool queue_full_result = is_queue_full(q); 
    pthread_mutex_unlock(q->queue_mutex); 

    if (queue_full_result){ 
     return true; 
    } else { 
     pthread_yield(); 
     sleep(sleep_time); 
    } 
} 
+0

Ho preso in considerazione questo, ma ho abbandonato questa idea dal momento che pensavo che questo fosse un uso intensivo della CPU. Volevo un modo più pulito per raggiungere questo obiettivo, non occupato a leggere lo stato della coda. –

+0

Questo è possibile solo se si può o si è autorizzati o in grado di scrivere il proprio 'is_queue_full'. Inoltre, l'attesa non dovrebbe essere così brutta nel tuo codice di prova non produttivo. Ma poiché questa sembra essere la tua coda, potremmo scrivere un approccio asincrono. Potresti darmi i nomi dei tuoi mutex e le condizioni per controllare la "pienezza"? – Superlokkus

+0

Sto arrivando alla conclusione che non esiste un modo corretto per farlo senza dover modificare le API della coda stessa. Quindi preferirei il metodo sopra. Comunque voglio prendere in considerazione la tua opinione su 'while (! Is_queue_full (q)) pthread_yield();' - questo è un miglioramento? –

2

Dal momento che il code utilizza pthread_cond_signal, deve essere in possesso di un blocco pure. Quindi il tuo test case dovrebbe semplicemente tenere il lucchetto, creare il produttore, quindi attendere il segnale. Dopo che il produttore ha generato il segnale, rilascia il lucchetto e crea un consumatore.

void test() { 
    pthread_mutex_lock(q_lock); 
    //  Blocks on the same queue lock the producer and 
    //  consumer would use. 
    create_producer(); 
    //  The producer will block on the queue lock when 
    //  it tries to write to the queue. 
    do { 
     pthread_cond_wait(q_write_cond, q_lock); 
     // Mimic a blocked queue_read, and wait for the 
     // producer to signal. This will release the lock 
     // and allow the producer to progress. 
    } while (!q_is_full()); 
    //  The queue is now full, lock is held since 
    //  pthread_cond_wait returned. 
    pthread_mutex_unlock(q_lock); 
    //  Release the queue lock, allow the consumer to 
    //  operate unhindered. 
    create_consumer(); 
    //  The consumer will proceed to drain the queue. 
}