286 lines
11 KiB
C++
286 lines
11 KiB
C++
//! \~\file pipipelinethread.h
|
||
//! \~\ingroup Thread
|
||
//! \~\brief
|
||
//! \~english Thread-based pipeline for multi-stage data processing
|
||
//! \~russian Потоковый конвейер для многоэтапной обработки данных
|
||
/*
|
||
PIP - Platform Independent Primitives
|
||
Class for create multihread pipeline
|
||
Andrey Bychkov work.a.b@yandex.ru
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU Lesser General Public License as published by
|
||
the Free Software Foundation, either version 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU Lesser General Public License for more details.
|
||
|
||
You should have received a copy of the GNU Lesser General Public License
|
||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#ifndef PIPIPELINETHREAD_H
|
||
#define PIPIPELINETHREAD_H
|
||
|
||
#include "piconditionvar.h"
|
||
#include "piqueue.h"
|
||
#include "pithread.h"
|
||
|
||
|
||
//! \~\ingroup Thread
|
||
//! \~english Thread-based pipeline template class for multi-stage data processing
|
||
//! \~russian Шаблонный класс потокового конвейера для многоэтапной обработки данных
|
||
//! \~\details
|
||
//! \~english
|
||
//! Pipeline thread template class for processing data through multiple stages in separate threads.
|
||
//! \~Each stage processes incoming data and passes it to the next stage via event notification.
|
||
//! \~russian
|
||
//! Шаблонный класс конвейерного потока для многоэтапной обработки данных в отдельных потоках.
|
||
//! Каждый этап обрабатывает входные данные и передает их следующему этапу через event-уведомления.
|
||
template<typename Tin, typename Tout>
|
||
class PIPipelineThread: public PIThread {
|
||
PIOBJECT_SUBCLASS(PIPipelineThread, PIThread);
|
||
|
||
public:
|
||
//! \~english Constructs pipeline thread
|
||
//! \~russian Создает конвейерный поток
|
||
PIPipelineThread() {
|
||
cnt = 0;
|
||
max_size = 0;
|
||
wait_next_pipe = false;
|
||
}
|
||
|
||
//! \~english Stops the stage thread and may terminate it forcibly if it does not finish in time.
|
||
//! \~russian Останавливает поток стадии и может принудительно завершить его, если он не завершится вовремя.
|
||
~PIPipelineThread() {
|
||
stop();
|
||
cv.notifyAll();
|
||
if (!waitForFinish(1000)) {
|
||
piCoutObj << "terminating self thread";
|
||
terminate();
|
||
}
|
||
}
|
||
|
||
//! \~english Connects to next pipeline stage via event notification
|
||
//! \~russian Подключает к следующему этапу конвейера через event-уведомления
|
||
template<typename T>
|
||
void connectTo(PIPipelineThread<Tout, T> * next) {
|
||
CONNECT3(void, Tout, bool, bool *, this, calculated, next, enqueue);
|
||
}
|
||
|
||
//! \~\handlers
|
||
//! \~\{
|
||
|
||
//! \~english Event handler for data enqueue
|
||
//! \~russian Обработчик события добавления данных в очередь
|
||
//! \~\details
|
||
//! \~english
|
||
//! For bounded queues, \a wait decides whether to block until space is available or to drop the item immediately.
|
||
//! \~If \a overload is not null, it is set to \c true when the item is rejected because the queue is full.
|
||
//! \~russian
|
||
//! Для ограниченных очередей \a wait определяет, ждать ли освобождения места или сразу отбросить элемент.
|
||
//! \~Если \a overload не равен null, он получает значение \c true, когда элемент отклонен из-за переполнения очереди.
|
||
EVENT_HANDLER3(void, enqueue, const Tin &, v, bool, wait, bool *, overload) {
|
||
mutex.lock();
|
||
// piCoutObj << "enque" << overload;
|
||
if (wait && max_size != 0) {
|
||
mutex_wait.lock();
|
||
while (in.size() >= max_size)
|
||
cv_wait.wait(mutex_wait);
|
||
mutex_wait.unlock();
|
||
}
|
||
if (max_size == 0 || in.size() < max_size) {
|
||
in.enqueue(v);
|
||
cv.notifyAll();
|
||
if (overload) *overload = false;
|
||
} else {
|
||
if (overload) *overload = true;
|
||
}
|
||
mutex.unlock();
|
||
}
|
||
|
||
//! \~\}
|
||
//! \~\events
|
||
//! \~\{
|
||
|
||
//! \~\fn void calculated(const Tout & v, bool wait, bool * overload)
|
||
//! \~\brief
|
||
//! \~english Emitted when processing result is ready
|
||
//! \~russian Генерируется когда результат обработки готов
|
||
//! \~\details
|
||
//! \~english Raised after \a calc() succeeds.
|
||
//! \~russian Вызывается после успешного завершения \a calc().
|
||
//! \~\note
|
||
//! \~english The \a wait flag is propagated to a downstream stage and does not affect this stage's own queue.
|
||
//! \~russian Флаг \a wait передается следующей стадии и не влияет на собственную очередь этой стадии.
|
||
EVENT3(calculated, const Tout &, v, bool, wait, bool *, overload);
|
||
|
||
//! \}
|
||
|
||
|
||
//! \~english Enqueue data for processing
|
||
//! \~russian Добавляет данные в очередь на обработку
|
||
void enqueue(const Tin & v, bool wait = false) { enqueue(v, wait, nullptr); }
|
||
|
||
//! \~english Returns pointer to counter
|
||
//! \~russian Возвращает указатель на счетчик
|
||
const ullong * counterPtr() const { return &cnt; }
|
||
|
||
//! \~english Returns items processed counter
|
||
//! \~russian Возвращает количество обработанных элементов
|
||
ullong counter() const { return cnt; }
|
||
|
||
//! \~english Returns whether the input queue is empty.
|
||
//! \~russian Возвращает, пуста ли входная очередь.
|
||
bool isEmpty() {
|
||
bool ret;
|
||
mutex.lock();
|
||
ret = in.isEmpty();
|
||
mutex.unlock();
|
||
return ret;
|
||
}
|
||
|
||
//! \~english Returns the current input queue size.
|
||
//! \~russian Возвращает текущий размер входной очереди.
|
||
int queSize() {
|
||
int ret;
|
||
mutex.lock();
|
||
ret = in.size();
|
||
mutex.unlock();
|
||
return ret;
|
||
}
|
||
|
||
//! \~english Clear input queue
|
||
//! \~russian Очищает входную очередь
|
||
void clear() {
|
||
mutex.lock();
|
||
mutex_wait.lock();
|
||
in.clear();
|
||
cv_wait.notifyAll();
|
||
mutex_wait.unlock();
|
||
mutex.unlock();
|
||
}
|
||
|
||
//! \~english Stops calculation and waits for thread finish
|
||
//! \~russian Останавливает вычисления и ожидает завершения потока
|
||
//! \~\note
|
||
//! \~english If the stage does not stop within \a wait_delay, it may be terminated forcibly.
|
||
//! \~russian Если стадия не остановится за \a wait_delay, она может быть принудительно завершена.
|
||
void stopCalc(int wait_delay = 100) {
|
||
if (isRunning()) {
|
||
stop();
|
||
cv.notifyAll();
|
||
if (!waitForFinish(wait_delay)) {
|
||
mutex_last.unlock();
|
||
mutex.unlock();
|
||
terminate();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//! \~english Returns a copy of the last successfully calculated output.
|
||
//! \~russian Возвращает копию последнего успешно вычисленного выхода.
|
||
//! \~\note
|
||
//! \~english This snapshot is separate from the input queue.
|
||
//! \~russian Этот снимок хранится отдельно от входной очереди.
|
||
Tout getLast() {
|
||
Tout ret;
|
||
mutex_last.lock();
|
||
ret = last;
|
||
mutex_last.unlock();
|
||
return ret;
|
||
}
|
||
|
||
|
||
//! \~english Returns the configured input queue limit.
|
||
//! \~russian Возвращает настроенный предел входной очереди.
|
||
//! \~\note
|
||
//! \~english Value \c 0 means the queue is unbounded.
|
||
//! \~russian Значение \c 0 означает, что очередь не ограничена.
|
||
uint maxQueSize() { return max_size; }
|
||
|
||
//! \~english Sets the input queue limit.
|
||
//! \~russian Устанавливает предел входной очереди.
|
||
//! \~\note
|
||
//! \~english Value \c 0 removes the limit. If the queue already exceeds the new limit, it is resized immediately.
|
||
//! \~russian Значение \c 0 снимает ограничение. Если очередь уже превышает новый предел, она немедленно приводится к нему.
|
||
void setMaxQueSize(uint count) {
|
||
mutex.lock();
|
||
max_size = count;
|
||
if (max_size > 0 && in.size() > max_size) in.resize(max_size);
|
||
mutex.unlock();
|
||
}
|
||
|
||
//! \~english Returns if waiting for next pipeline stage
|
||
//! \~russian Возвращает ожидает ли следующий этап конвейера
|
||
bool isWaitNextPipe() { return wait_next_pipe; }
|
||
|
||
//! \~english Sets whether to wait for next pipeline stage
|
||
//! \~russian Устанавливает флаг ожидания следующего этапа конвейера
|
||
//! \~\details
|
||
//! \~english Sets whether the \a calculated() signal requests waiting in the downstream stage.
|
||
//! \~russian Устанавливает, должен ли сигнал \a calculated() запрашивать ожидание на следующей стадии.
|
||
//! \~\note
|
||
//! \~english This flag only affects downstream propagation and does not change how this stage accepts input.
|
||
//! \~russian Этот флаг влияет только на передачу вниз по конвейеру и не меняет правила приема входа этой стадией.
|
||
void setWaitNextPipe(bool wait) { wait_next_pipe = wait; }
|
||
|
||
|
||
protected:
|
||
//! \~english Calculates one output from queued input \a v.
|
||
//! \~russian Вычисляет один выход из очередного входного элемента \a v.
|
||
//! \~\note
|
||
//! \~english Leave \a ok equal to \c true to publish the result, or set it to \c false to drop this input without forwarding.
|
||
//! \~russian Оставьте \a ok равным \c true, чтобы опубликовать результат, или установите \a ok в \c false, чтобы отбросить этот вход
|
||
//! без пересылки дальше.
|
||
virtual Tout calc(Tin & v, bool & ok) = 0;
|
||
|
||
//! \~english Maximum queue size (0 means unlimited)
|
||
//! \~russian Максимальный размер очереди (0 означает без ограничений)
|
||
uint max_size;
|
||
|
||
private:
|
||
void begin() override { cnt = 0; }
|
||
|
||
void run() override {
|
||
mutex.lock();
|
||
while (in.isEmpty()) {
|
||
cv.wait(mutex);
|
||
if (terminating) {
|
||
mutex.unlock();
|
||
return;
|
||
}
|
||
}
|
||
mutex_wait.lock();
|
||
Tin t = in.dequeue();
|
||
mutex.unlock();
|
||
cv_wait.notifyAll();
|
||
mutex_wait.unlock();
|
||
bool ok = true;
|
||
Tout r = calc(t, ok);
|
||
if (ok) {
|
||
mutex_last.lock();
|
||
last = r;
|
||
mutex_last.unlock();
|
||
cnt++;
|
||
// piCoutObj << "calc ok";
|
||
calculated(r, wait_next_pipe);
|
||
}
|
||
// piCoutObj << "run ok";
|
||
}
|
||
|
||
PIMutex mutex, mutex_wait;
|
||
PIConditionVariable cv, cv_wait;
|
||
PIMutex mutex_last;
|
||
bool wait_next_pipe;
|
||
ullong cnt;
|
||
PIQueue<Tin> in;
|
||
Tout last;
|
||
};
|
||
|
||
#endif // PIPIPELINETHREAD_H
|