Как организовать работу потоков в qt?

Пишу консольное приложение, которое должно работать в фоновом режиме на сервере – суть следующая:

Есть несколько математических алгоритмов (условно 5), которые производят некоторый расчет над данными, которые находятся в БД. Результаты расчета записываю в эту же БД. Сами алгоритмы реализованы, работа с БД тоже.

Мне нужно организовать работу этих алгоритмов в разных потоках, каждый алгоритм отдельный поток.

Вопрос в следующем, как организовать работу потоков, чтобы при одновременной работе потоков не было конфликтов с БД, с записью информации в лог файл?

Классlogger.h – работа с лог файлом

class Logger:public QObject
{
    Q_OBJECT
public:
    explicit Logger(QObject* parent,QString f_Name);
    ~Logger();

    void setShowDateTime(boolvalue);  
private:
    QFile *file;
    bool m_showDate;

public slots:
    void writeM(const QString &value);
    void writeERR(const QString &value);    
};

Классdb_manager.h– работа с БД и описание процедур для того чтобы достать и записать данные в БД

class Db_manager: public QObject
{
    Q_OBJECT
public:
    explicit Db_manager(QObject*,Logger *log);
    ~Db_manager();
    bool checkIfConnected();
    QString Listspi_getparams();//получитьконстантыизтаблицы_Params
    QVector<QStringList> spi_getparamsforcalpressureecn(intid_device); //получитьпараметрыизтаблицы_production

---много различных функций для ппарсинга и записиси в БД--

    intspi_insert_all_params_va(QStringListdata);
    intspi_insert_lastparams(QStringListdata);    
public slots:
    void сonnectToServerRequested(QString,QString,int,QString,QString,QString);
    void disconnectFromServerRequested();

signals:
    void serverConnected();
    void serverErrorWithConnection(QString);
    void serverDisconnected();

private:
    bool connectToServerPSQL(QString,int,QString,QString,QString);
    void disconnectFromServer();
    QSqlErrorgetLastError();

    QSqlDatabase db;
    Logger *logger;
};

Работа второго алгоритма

classVAflowMeter:public QObject
{
    Q_OBJECT
public:
    explicit VAflowMeter(QObject* parent,Db_manager* dbc,Logger* log);
    ~VAflowMeter();

private:
    Db_manager* db;
    Logger* logger;
    secondstage* unitTwo;//вся математика для алгоритма
    bool stoped;
    QMutex m_mutex;

public slots:
    void process();
    //voidstop();

signals:
    void finished();    
};

Работа первого алгоритма

class instAnalys:public QObject
{
    Q_OBJECT
public:
    explicit instAnalys(QObject*parent,Db_manager* dbc,Logger* log);
    ~instAnalys();    
private:
    Db_manager *db;
    Logger *logger;
    instAnal *instAn;//математика
    bool stoped;
    QMutex m_mutex;

public slots:
    void process();
    //voidstop();

signals:
    void finished();    
};

Пока пришел к такому неправильному решению:

Класс, в котором я работаю со всеми потоками

void allworks::start Work()
{
    QThread *va_thread = new QThread();
    VAflowMeter *vaflowmeter=new VAflowMeter(0, db_manager, logger);
    
    vaflowmeter->moveToThread(va_thread);
    connect(va_thread,SIGNAL(started()), vaflowmeter, SLOT(process()));
    connect(vaflowmeter,SIGNAL(finished()), va_thread, SLOT(quit()));
    va_thread->start();
    
    QThread *ia_thread = new QThread();
    instAnalys* instAnalys=new instAnalys(0, db_manager, logger);
    
    instAnalys->moveToThread(ia_thread);
    connect(ia_thread, SIGNAL(started()), instAnalys, SLOT(process()));
    connect(instAnalys, SIGNAL(finished()), ia_thread, SLOT(quit()));
    ia_thread->start();
}

Как разграничить доступ к БД и лог файлу из разных потоков?

Вот к чему пришел после помощи:

class DB {
public:
    spi_procedure1();//функции работы с БД
    ...
    spi_procedureN();
public slots:
    void setParam1(QStringList) 
    {
        //получили сигнал от первого алгоритма resultChanged
        //произвели запись в БД
    }
    ...
    void setParam2(QStringList);//второй алгоритм
    void getParam1()//считываем параметры
    {
        //получили сигнал от алгоритма loadParams то нужно считать параметры
        //получаем из бд параметры в QStringList strList и отправляем через сигнал
        emit param1Changed(strList);
    }
    ... 
    void getParam2();//второй алгоритм
signals:
    param1Changed(QStringList);//первый алгоритм
    param2Changed(QStringList);//второй алгоритм
    logErr(QString);//сигнал в лог об ошибке
    logMes(QString);//сообщение в лог
}

class Alg1Worker {
public slots:    
    setParams(QStringList strList)
    {
        //получили сигнал от БД что параметры изменились и записли их
        alg1params = strList;       
    }
    process()
    {
        emit loadParams();//отправляем в БД запрос о том что нужно получить новые параметры
        //как понять что параметры изменились и пора начинать расчет для новых параметров?
        ...
        //производим расчет
        emit resultChanged(QStringList);//отправляем расчетные данные на запись в БД
        //нужно опять сделать ожидаение, чтобы пока данные не записались не начинать следующий цикл расчета
    }
signals:
    resultChanged(QStringList);
    loadParams();
    logErr(QString);//сигнал в лог об ошибке
    logMes(QString);//сообщение в лог
private:
    // Только параметры для работы алгоритма и никаких 
    // указателей на объекты БД и Лога
    QStringList alg1params;
    QStringList mathalgor1();//вся математика на входе работаем с параметрами alg1params
}

class Alg2Worker {} //аналогично

class Log {
public:    
    ...    
public slots:
    void writeM(const QString &value);
    void writeERR(const QString &value);
    ....
}

DB db;
Log log;
Alg1Worker alg1();
Alg2Worker alg2();
...
//alg1
connect(&alg1, &Alg1Worker::loadParams, &db, &DB::getParam1);//из process() попросили БД вытащить параметры
connect(&db, &DB::param1Changed(QStringList), &alg1, &Alg1Worker::setParams(QStringList));//БД вытащила параметры и отправила в закрытые члены alg1
connect(&alg1, &Alg1Worker::resultChanged(QStringList), &db, &DB::setParam1(QStringList));//отправляем расчитанные параметры для записи в БД
//alg2
connect(&alg2, &Alg2Worker::loadParams, &db, &DB::getParam2);//из process() попросили БД вытащить параметры
connect(&db, &DB::param2Changed(QStringList), &alg2, &Alg2Worker::setParams(QStringList));//БД вытащила параметры и отправила в закрытые члены alg1
connect(&alg2, &Alg2Worker::resultChanged(QStringList), &db, &DB::setParam2(QStringList));//отправляем расчитанные параметры для записи в БД
//log
//mess
connect(&alg1, &Alg1Worker::logMes(QString), &log, &Log::writeM(QString));
connect(&alg2, &Alg2Worker::logMes(QString), &log, &Log::writeM(QString));
connect(&db, &DB::logMes(QString), &log, &Log::writeM(QString));
//err
connect(&alg1, &Alg1Worker::logErr(QString), &log, &Log::writeERR(QString));
connect(&alg2, &Alg2Worker::logErr(QString), &log, &Log::writeERR(QString));
connect(&db, &DB::logErr(QString), &log, &Log::writeERR(QString));


QThread *alg1_thread = new QThread();
Alg1Worker *alg1=new Alg1Worker();
    
alg1->moveToThread(alg1_thread);
connect(alg1_thread,SIGNAL(started()), alg1, SLOT(process()));
connect(alg1,SIGNAL(finished()), alg1_thread, SLOT(quit()));
alg1_thread->start();
    
QThread *alg2_thread = new QThread();
Alg2Worker* alg2=new Alg2Worker();
    
alg2->moveToThread(alg2_thread);
connect(alg2_thread, SIGNAL(started()), alg2, SLOT(process()));
connect(alg2, SIGNAL(finished()), alg2_thread, SLOT(quit()));
alg2_thread->start();

Ответы (1 шт):

Автор решения: Alexander Chernin

В целом архитектура должна соответствовать принципу "разделяй и властвуй" (по-русски, инкапсулируй/абстрагируй и влавствуй), то есть вот такого

class VAflowMeter : public QObject
{
    explicit VAflowMeter(QObject* parent, Db_manager* dbc, Logger* log);

быть не должно (в данном случае).

В Qt для реализации данной древней концепции поможет механизм Сигналов-Слотов

У вас есть класс работы с БД, данные из которого могут получаться, например, при помощи 1) прямого запроса, 2) при изменении данных, - объект БД сигнализирует об этом подписчикам. Кроме того, при записи данных в БД можно использовать слоты

class DB {
public:
    getParam1();
    ...
    getParamN();
public slots:
    void setParam1(); // Внутри, при необходимости, мьютекс и сигнал о том, что данный параметр изменился
    void setParam2(); // ----//----
    ...
signals:
    param1Changed(<Value>);
    param2Changed(<Value>);
}

Для лога аналогично.

Поток с алгоритмом тоже взаимодействует с внешним миром через сигналы-слоты:

class Alg1Worker {
signals:
    resultChanged();
public slots:    
    setParam1();
    ...
    setParamsN();
    process();
private:
    // Только параметры для работы алгоритма и никаких 
    // указателей на объекты БД и Лога
}

Теперь необходимо все это соединить. Где-то во внешнем файле (например в main.cpp):

DB db;
Log log;
Alg1Worker alg1(<параметры инициализации алгоритма, например db.getParam1()>);
Alg2Worker alg2(<... db.getParam2()>);
...

connect(&alg1, &Alg1Worker::resultChanged, &db, &DB::setParam1);
connect(&alg2, &Alg1Worker::resultChanged, &db, &DB::setParam2);
// Для лога
connect(&alg1, &Alg1Worker::resultChanged, &log, &Log::setParam1);
connect(&alg2, &Alg2Worker::resultChanged, &log, &Log::setParam2);

// Или, если так удобнее, то можно использовать лямбду
connect(&alg1, &Alg1Worker::resultChanged, [&](<value>) {
    db.setParam1();
    log.setParam1();
});

// В обратную сторону:

connect(&db, &DB::param1Changed, &alg2, &Alg1::setParam2);
connect(&db, &DB::param2Changed, &alg1, &Alg1::setParam1);

// далее запуск потоков и так далее. 

Теперь все объекты заняты каждый своим делом и не мешают друг другу

Вариант работы process #1:

// переменные со знаком _ являются полями класса
process() {
    while(_active) {
        if (!hasParams()) { // Параметров нет
            emit requestParams();
            _waitForParams.wait(_mutex);
        } else { // Параметры есть
            // производим расчет
            emit resultChanged(<результат>);
        }
    }
}

Здесь _waitForParams объект класса QWaitCondition В этом варианте надо соединить requestParams со слотом в DB для получения необходимых данных

// Отправляем/Получаем прямой запрос на данные
connect(&alg1, &Alg1Worker::requestParams, &db, &DB::emitNextParams1, Qt::DirectConnetion);

connect(&db, &DB::nextParams1, &alg1, &Alg1Worker::setParams);

Где:

setParams(<Параметры>) {
    // Инициализируем поля класса нужными параметрами
    // Будим поток
    _waitForParams.wakeAll();
}

Вариант 2. Передаем объект БД (и только его), как объект только для чтения параметров расчета, тогда

process() {
    while(_active) {       
        auto params = _db->getNextParams();
        // расчет
        emit resultChanged(<результат>);
    }
}

Результат как и прежде отправляются в лог и ту же БД не мешая расчетами в потоках

// Отправляем данные в очередь для последовательной записи
connect(&alg1, &Alg1Worker, &log, &Log::writeResult1, Qt::QueueConnection);
→ Ссылка