Блок задач

12. Многопоточность

Сложность 6

Задача «Очередь данных»

Разработать класс, позволяющий производить многопоточное буферизированное чтение данных. Данные читаются блоками произвольного размера, произвольное количество блоков за раз. Типичный пример подобных данных - последовательность сжатых изображений разного размера (например, в формате jpeg).

Постановка задачи

Задание предполагает одновременную работу двух потоков выполнения: один поток (поток-читатель) считывает данные в очередь, второй (поток-писатель) - с некоторой периодичностью проверяет очередь, выбирает из неё очередные данные, и записывает их в файлы.

Очередь выполняет здесь роль регулятора, который:

  • позволяет согласовать скорости чтения и записи данных между потоками,
  • гарантирует порядок и целостность передаваемых данных.
  • синхронизирует доступ к данным из разных потоков выполнения.

Класс очереди должен быть спроектирован в рамках концепции RAII, и являться владельцем буфера данных.

Примерный прототип класса очереди:

class DataFIFO {
   DataFIFO(size_t bufferSize, size_t maxBlocks);
   ~DataFIFO();
   void * getFree(size_t size);
   void addReady(void * data);
   void * getReady(size_t & size);
   void addFree(void * data);
};
  • DataFIFO(bufferSize, maxBlocks) - выделяет буфер данных заданного размера, с максимальным количеством блоков.
  • ~DataFIFO() - деструктор, освобождает память.
  • getFree(size) - метод, вызываемый потоком-писателем, для проверки наличия size свободных байт для данных. Если требуемое количество байт свободно, то функция возвращает начальный адрес блока, иначе null.
  • addReady(data) - метод, вызываемый потоком-писателем, помечающий, что запрошенный ранее блок заполнен и готов к передаче.
  • getReady(size) - метод, вызываемый потоком-читателем, для получения указателя на очередной готовый блок данных. Если готового блока нет, то функция возвращает null. Через входной аргумент size возвращается размер блока.
  • addFree(data) - метод, вызываемый потоком-читателем, помечающий, что полученный ранее блок данных обработан, место освобождёно и может повторно использоваться очередью.

Тестирование

Необходимо реализовать ряд тестов в порядке усложнения:

  • в каждом тесте на вход очереди блоки данных различной длины (например последовательность jpeg-изображений, считанных в бинарном режиме), а на выходе - эти данные накапливаются (например сохраняется на диск в бинарном режиме, как jpeg файлы), и по окончании передачи проводится побайтовая сверка соответствующих блоков.
  • для базовой проверки функционала обязателен однопоточный тест, поочерёдное чтение/запись в одном потоке.
  • также необходим многопоточный тест, где чтение/запись производятся из разных потоков.

Дополнительный бал за тест на "необычные" паттерны поведения потоков читателя и писателя:

  • тест на переполнение очереди потоком-писателем.
  • писатель много раз вызывает getFree(), затем заполняет данные, а затем в произвольном порядке помечает блоки как готовые вызовами addReady().
  • тест на опустошении очереди потоком-читателем.
  • читатель много раз вызывает getReady(), сохраняет данные, а затем в произвольном порядке помечает блоки как свободные вызовами addFree().