/*
* cp1.c
*/
#include <stdio.h>
#include <pthread.h>
int data_ready = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
void* consumer(void *notused){
printf("Это поток-потребитель...\n");
while (1) {
pthread_mutex_lock(&mutex);
while (!data_ready) {
pthread_cond_wait(&condvar, &mutex);
}
// Обработать данные
printf("Потребитель: получил данные от производителя\n");
data_ready = 0;
pthread_cond_signal(&condvar);
pthread_mutex_unlock(&mutex);
}
}
void* producer (void *notused) {
printf("Это поток-производитель...\n");
while (1) {
// Получить данные от оборудования
// (мы имитируем это при помощи sleep(1))
sleep(1);
printf("Производитель: получил данные от h/w\n");
pthread_mutex_lock(&mutex);
while (data_ready) {
pthread_cond_wait(&condvar, &mutex);
}
data_ready = 1;
pthread_cond_signal(&condvar);
pthread_mutex_unlock(&mutex);
}
}
main() {
printf(
"Начало примера с производителем и потребителем...\n");
// Создать поток-производитель и поток-потребитель
pthread_create(NULL, NULL, producer, NULL);
pthread_create(NULL, NULL, consumer, NULL);
// Дать потокам немного повыполняться
sleep(20);
}
Этот пример в значительной степени похож на программу с применением ждущей блокировки, с небольшими отличиями (мы добавили несколько вызовов printf(), а также функцию main(), чтобы программа могла работать!) Первое отличие, которое бросается в глаза, — здесь использован новый тип данных, pthread_cond_t
. Это просто декларация для условной переменной; мы назвали нашу условную переменную condvar.
Следующее, что видно из примера, — это то, что структура «потребителя» идентична таковой в предыдущем примере с ждущей блокировкой. Мы заменили функции pthread_sleepon_lock() и pthread_sleepon_unlock() на стандартные мутекс-ориентированные версии (pthread_mutex_lock() и pthread_mutex_unlock()). Функция pthread_sleepon_wait() была заменена на функцию pthread_cond_wait().
Основное различие здесь состоит в том, что библиотека ждущих блокировок имеет скрытый внутренний мутекс, а при использовании условных переменных мутекс передается явно. Последний способ дает нам больше гибкости.
И, наконец, обратите внимание на то, что мы использовали функцию pthread_cond_signal() вместо функции pthread_sleepon_signal() (опять же, с явной передачей мутекса).
В разделе о ждущих блокировках мы обещали обсудить различие между функциями pthread_sleepon_broadcast() и pthread_sleepon_signal(). Заодно поговорим и о различии между двумя аналогичными функциями, имеющими отношение к условным переменным: pthread_cond_signal() и pthread_cond_broadcast().
В двух словах, функция в варианте «signal» разблокирует только один поток. Например, если бы несколько потоков находилось в ожидании по функции «wait», и некий поток вызвал бы функцию pthread*_signal(), то был бы разблокирован только один из ждущих потоков. Который из них? Тот, у которого наивысший приоритет. Если имеется два или более потоков с одинаковым приоритетом, порядок «пробуждения» будет не определен. Применение же варианта pthread*_broadcast() приведет к тому что будут разблокированы все ожидающие потоки.
Разблокировать все потоки может показаться излишним. Но с другой стороны, разблокировать только один (причем случайный поток тоже не совсем корректно.
Поэтому мы должны думать, где имеет смысл использовать какой вариант. Очевидно, что если у вас только один ждущий поток, как у нас и было во всех вариантах «потребителя», функция pthread*_signal() прекрасно справится — будет разблокирован один поток, и как раз тот, который нужно (потому что других просто нет).
В ситуации с несколькими потоками в первую очередь следует выяснить: а почему они ждут? Обычно на этот вопрос есть два ответа:
• все потоки рассматриваются как эквивалентные и реально образуют пул доступных потоков, готовых к обработке некоторого запроса;
• все потоки являются уникальными, и каждый из них ждет соблюдения своего специфического условия.
В первом случае мы можем представить себе, что код всех потоков имеет примерно следующий вид:
/*
* cv1.c
*/
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex_data = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv_data = PTHREAD_COND_INITIALIZER;
int data;
thread1() {
for (;;) {
pthread_mutex_lock(&mutex_data);
while (data == 0) {
pthread_cond_wait(&cv_data, &mutex_data);
}
// Сделать что-нибудь
pthread_mutex_unlock(&mutex_data);
}
}
В этом случае абсолютно неважно, который именно из потоков получит данные — главное, чтобы хотя бы один сделал это и произвел над этими данными необходимые действия.
Однако, если ваш код подобен приведенному ниже, все будет несколько по-иному:
/*
* cv2.c
*/
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex_xy = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv_xy = PTHREAD_COND_INITIALIZER;
int x, y;
int isprime(int);
thread1() {
for (;;) {
pthread_mutex_lock(&mutex_xy);
while ((x > 7) && (y != 15)) {
pthread_cond_wait(&cv_xy, &mutex_xy);
}
// Сделать что-нибудь
pthread_mutex_unlock(&mutex_xy);
}