Как назначить 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);
}
}