Как назначить pthread_cond_signal() так, что он всегда следует за pthread_cond_wait() в другом потоке?

Я тестирую сценарий производителя-потребителя, когда производитель блокируется, когда он пытается записать в заполненную очередь. Я хочу проверить, что поток производителя просыпается правильно и работает в ожидании после того, как потребитель читает из полной очереди *. API-адрес для записи в очередь вызывает pthread_cond_wait() при обнаружении полной очереди, и API-интерфейс чтения сигнализирует условную переменную после чтения из очереди.

Как обеспечить появление Последовательности 3 по любой другой последовательности операций в тестовой среде?

введите описание изображения здесь

* Да, я хочу протестировать этот ограниченный сценарий отдельно; есть и другие тесты, которые проверяют функциональность всей очереди, этот тест в дополнение к этим.

Подробнее -
Существует один мьютекс, управляющий очередью. Там 2 условные переменные - одна для записи записи (все записи), одна для сигнал считывается (все считывается). Блокировка queue_write API для чтения condvar, если очередь заполнена. Блокировка queue_read на блоке write condvar, если очередь пуста. Вся сигнализация происходит под эгида мьютекса.
В очереди больше нюансов, но с целью установления контекста для этого вопроса, это адекватное резюме функционирования очереди.

Ответ 1

Поскольку ваша очередь использует pthread_cond_signal, она также должна удерживать блокировку. Таким образом, ваш тестовый пример должен просто удерживать блокировку, создавать производителя, а затем ждать сигнала. После того, как производитель генерирует сигнал, отпустите блокировку и создайте пользователя.

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

Ответ 2

Отредактировано (помните, что обработка ошибок вызовов pthread была опущена)

Вы можете добиться этого, проверив, заполнена ли очередь, с помощью функции, указанной в комментариях. Для этого ответа я буду считать его bool is_queue_full(const queue*).

В вашем тестовом примере вы можете гарантировать сценарий 3, создав производителя и создав потребителя, если и только тогда, когда очередь заполнена. подобно   bool is_queue_full (очередь *);//Нельзя использовать сам мьютекс, возможно, пометить его только для использования в режиме старта

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 будет просто проверять, заполнена ли очередь, а если нет, будет ждать, как и любой читатель, до тех пор, пока ваш писатель, как продюсер, не переполнит его. Тогда ваш тестовый файл может произвести потребителей с чем-то вроде test_writer_woke_up  void intern_consume_stuff (очередь q);/Ваша стажерская функция, которая забирает материал из очереди,   но не заботятся о сихронизации aka мьютексов и 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;
}

Если вы использовали абсолютное время ожидания, потому что вам приходилось пересчитывать относительные тайминги или упрощать вещи, вы могли бы заменить цикл for этим наивным подходом

//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);
    }
}