Когда программа выполняется, она выводит на консоль случайную последовательность из 100 000 букв «А», «С», «G» и «T» и затем завершает свою работу. Для того чтобы понять, что происходит на самом деле, мы можем отключить вывод указанной последовательности и вместо этого выводить на консоль букву «P» при генерации каждого байта первым потоком и букву «с» при чтении байта вторым потоком. И ради максимального упрощения ситуации мы можем использовать меньшие значения параметров
Например, при выполнении программы, когда
Другой подход к решению проблемы синхронизации работы потока, формирующего данные, и потока, принимающего данные, состоит в применении классов
const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];
QWaitCondition bufferIsNotFull;
QWaitCondition bufferIsNotEmpty;
QMutex mutex;
int usedSpace = 0;
Кроме буфера мы объявляем два объекта
01 void Producer::run
02 {
03 for (int i = 0; i < DataSize; ++i) {
04 mutex.lock;
05 while (usedSpace == BufferSize)
06 bufferIsNotFull.wait(&mutex);
07 buffer[i % BufferSize] = "ACGT"[uint(rand) % 4];
08 ++usedSpace;
09 bufferIsNotEmpty.wakeAll;
10 mutex.unlock;
11 }
12 }
Работу потока, формирующего данные, мы начинаем с проверки заполнения буфера. Если он заполнен, мы ждем возникновения условия «буфер не заполнен». Когда это условие удовлетворяется, мы записываем один байт в буфер, увеличиваем на единицу
Мы используем мьютекс для контроля любого доступа к переменной
В этом примере мы могли бы заменить цикл
while (usedSpace == BufferSize)
bufferIsNotFull.wait(&mutex);
на инструкцию
if (usedSpace == BufferSize) {
mutex.unlock;
bufferIsNotFull.wait;
mutex.lock;
}
Однако это не будет правильно работать, как только мы станем использовать несколько потоков, формирующих данные, поскольку другой такой поток может захватить мьютекс сразу же после вызова функции
01 void Consumer::run
02 {
03 for (int i = 0; i < DataSize; ++i) {
04 mutex.lock;
05 while (usedSpace == 0)
06 bufferIsNotEmpty.wait(&mutex);
07 cerr << buffer[i % BufferSize];
08 --usedSpace;
09 bufferIsNotFull.wakeAll;
10 mutex.unlock;
11 }
12 cerr << endl;
13 }
Поток—приемник работает в точности наоборот относительно первого потока: он ожидает возникновения условия «буфер не пустой» и возобновляет работу любого потока, ожидающего условия «буфер не заполнен».