const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];
Поток, являющийся поставщиком данных, записывает данные в буфер, пока он не заполнится, и затем повторяет эту процедуру сначала, переписывая существующие данные. Поток, принимающий данные, считывает данные по мере их поступления. Это проиллюстрировано на рис. 18.2 для небольшого 16-байтового буфера.
Необходимость синхронизации для примера взаимодействия потоков, один из которых формирует данные, а другой их считывает, обусловлена двумя причинами: если формирующий данные поток работает слишком быстро, он станет переписывать данные, которые еще не считал поток—приемник; если поток—приемник считывает данные слишком быстро, он перегонит другой поток и станет считывать «мусор».
Грубый способ решения этой проблемы состоит в том, чтобы сначала заполнить буфер и затем ждать, пока поток—приемник не считает буфер целиком и так далее. Однако в многопроцессорных системах это не позволит обоим потокам работать одновременно с разными частями буфера.
Одни из эффективных способов решения этой проблемы заключается в использовании двух семафоров:
QSemaphore freeSpace(BufferSize);
QSemaphore usedSpace(0);
Семафор
В этом примере каждый байт рассматривается как один ресурс. В реальном приложении мы, вероятно, использовали бы более крупные блоки памяти (например, по 64 или 256 байт) для снижения затрат, обусловленных применением семафоров.
01 void Producer::run
02 {
03 for (int i = 0; i < DataSize; ++i) {
04 freeSpace.acquire;
05 buffer[i % BufferSize] = "ACGT"[uint(rand) % 4];
06 usedSpace.release;
07 }
08 }
Каждая итерация при работе потока, формирующего данные, начинается с захвата одного «свободного» байта. Если весь буфер заполнен данными, которые не считаны потоком—приемником, вызов функции
01 void Consumer::run
02 {
03 for (int i = 0; i < DataSize; ++i) {
04 usedSpace.acquire;
05 cerr << buffer[i % BufferSize];
06 freeSpace.release;
07 }
08 cerr << endl;
09 }
Работу потока—приемника мы начинаем с захвата одного «использованного» байта. Если буфер не содержит данных для чтения, вызов функции
01 int main
02 {
03 Producer producer;
04 Consumer consumer;
05 producer.start;
06 consumer.start;
07 producer.wait;
08 consumer.wait;
09 return 0;
10 }
Наконец, в функции