2016-01-13 4 views
12

Ich teste ein Producer-Consumer-Szenario, bei dem der Producer blockiert, wenn er versucht, in eine Warteschlange zu schreiben, die voll ist. Ich möchte testen, ob der Produzententhread richtig aufwacht und wie erwartet funktioniert, nachdem der Kunde aus der vollen Warteschlange * liest. Die Warteschlangenschreib-API ruft pthread_cond_wait() beim Erkennen der Warteschlange auf und die Lese-API signalisiert die bedingte Variable nach dem Lesen aus der Warteschlange.Wie programmiere ich ein pthread_cond_signal() so, dass es immer einem pthread_cond_wait() in einem anderen Thread folgt?

Wie stelle ich sicher, dass Sequence 3 über eine andere Sequenz von Operationen in meiner Testumgebung auftritt?

enter image description here

* Ja Ich möchte separat dieses begrenzten Szenario testen; Es gibt andere Tests, die die Funktionalität der gesamten Warteschlange testen, dieser Test ist zusätzlich zu diesen.

Weitere Details -
Es gibt eine einzige Mutex die Warteschlange regeln. Dort sind 2 bedingte Variablen - eine zum Schreiben zu schreiben (alle schreibt), eine zu Signal lesen (alle liest). Die queue_write API blockiert auf dem Lesen condvar, wenn die Warteschlange voll ist. Die queue_read-API blockiert write condvar, wenn die Warteschlange leer ist. Die gesamte Signalisierung erfolgt unter der Ägide des Mutex.
Es gibt viel mehr Nuance in der Warteschlange, aber zum Setzen von Kontext für diese Frage ist dies eine angemessene Zusammenfassung der Warteschlange funktioniert.

+0

Starten Sie nur den Erzeugerfaden, warten Sie, bis er blockiert ist, und starten Sie dann den Verbraucher? – EOF

+0

Gibt es API-Funktionen, mit denen Sie nur testen können, wenn die Warteschlange voll ist? – Superlokkus

+0

@EOF - Wie programmiere ich programmatisch, dass der Produzententhread jetzt blockiert ist? (Ich suche etwas jenseits "warten Sie auf eine angemessene Zeitmenge") –

Antwort

1

Edited (Be, dass die Fehlerbehandlung der pthread Anrufe bewusst weggelassen wurden)

Sie können dies erreichen, indem er die Prüfung, ob die Warteschlange voll ist, mit der Funktion, die Sie in den Kommentaren erwähnt. Für diese Antwort werde ich annehmen, dass es bool is_queue_full(const queue*) ist.

In Ihrem Testfall können Sie Szenario 3 garantieren, indem Sie den Producer erstellen und einen Consumer erstellen, wenn und nur wenn die Warteschlange voll ist. Wie bool is_queue_full (Warteschlange *); // Darf nicht den Mutex selbst, verwenden Sie es vielleicht markieren nur für internen Gebrauch

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 wird nur prüfen, ob die Warteschlange voll ist, und wenn nicht, warten, wie jeder Leser, bis Ihr Schriftsteller aka Produzent hat machte es voll.Dann kann Ihr Testfall die Verbraucher mit etwas wie test_writer_woke_up void intern_consume_stuff produzieren (Warteschlange q);/ Ihre intern Funktion, die Sachen aus der Warteschlange die absolute Zeit verwendet hat, nimmt, aber doesen't kümmern sich um die Synchronisations aka mutexes und 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; 
} 

Wenn warten, da Sie relative Zeiten neu zu berechnen haben, oder, um die Dinge zu vereinfachen könnten Sie die for-Schleife mit diesem naiven Ansatz ersetzen

//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

Ich habe darüber nachgedacht, aber diese Idee aufgegeben, da ich dachte, dies sei CPU-intensiv. Ich wollte einen saubereren Weg, um dies zu erreichen, d. H. Nicht beschäftigt, den Warteschlangenstatus zu lesen. –

+0

Dies ist nur möglich, wenn Sie Ihre eigene 'is_queue_full' schreiben können oder können oder können. Auch das busy-waiting sollte in Ihrem nicht-produktiven Test-Code nicht so schlecht sein. Aber da dies Ihre eigene Warteschlange zu sein scheint, könnten wir einen asynchronen Ansatz schreiben. Könnten Sie mir die Namen Ihrer Mutexe und Bedingung geben, um die "Fülle" zu überprüfen? – Superlokkus

+0

Ich komme irgendwie zu der Schlussfolgerung, dass es keine angemessene Möglichkeit gibt, dies zu tun, ohne die Warteschlangen-APIs selbst modifizieren zu müssen. Daher würde ich die obige Methode bevorzugen. Ich möchte jedoch Ihre Meinung zu 'while (! Is_queue_full (q)) pthread_yield();' - ist das eine Verbesserung? –

2

da der Warteschlange pthread_cond_signal verwendet, muss es eine Sperre auch abhalten. Dein Testfall sollte also einfach die Sperre halten, den Producer erstellen und dann auf das Signal warten. Nachdem der Produzent das Signal generiert hat, geben Sie die Sperre frei und erstellen Sie einen Konsumenten.

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. 
}