Russian Qt Forum

Qt => Базы данных => Тема начата: unkeep от Март 29, 2016, 17:41



Название: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: unkeep от Март 29, 2016, 17:41
Какие минусы использования пула соединений при многопоточной работе с бд(соединение создаётся для каждого потока)?

В общем случае, на сколько я понимаю, жертвуя памятью получаем прирост скорости за счёт повторного использования открытого соединения. Но может быть есть ещё нюансы, например зависимость скорости соединения от их количества?


Название: Re: connection pool плюсы и минусы
Отправлено: Old от Март 29, 2016, 17:48
Какие минусы использования пула соединений при многопоточной работе с бд(соединение создаётся для каждого потока)?
Минусов не знаю, сам так делаю. :)


Название: Re: connection pool плюсы и минусы
Отправлено: unkeep от Март 29, 2016, 17:54
Есть ли смысл закрывать соединение если им долгое время не пользуются?


Название: Re: connection pool плюсы и минусы
Отправлено: Old от Март 29, 2016, 18:00
Есть ли смысл закрывать соединение если им долгое время не пользуются?
Нет. Зачем? А если сразу после закрытия понадобиться выполнить запрос? :)
Как правило, рабочих потоков работающих с БД много не запускают, ну будет открыто 8 соединений...


Название: Re: connection pool плюсы и минусы
Отправлено: unkeep от Март 30, 2016, 10:11
можно код ревью?

ConnectionPool.h
Код
C++ (Qt)
#pragma once
#include <QSqlDatabase>
#include <QThread>
#include <QMap>
 
class ConnectionPool : public QObject
{
   Q_OBJECT
public:
   explicit ConnectionPool(const QString driverName,
                           const QString& dbName,
                           const QString& dbHost,
                           const quint16& dbPort,
                           const QString& userName,
                           const QString& userPassword,
                           QObject* parent = 0);
   ~ConnectionPool();
 
   QSqlDatabase getConnection();
   QSqlDatabase getConnectionForThread(QThread* thread);
 
private:
   QString generateConnectionName(QThread* thread) const;
   void onThreadDestroyed();
 
private:
   const QString _driverName;
   const QString _dbName;
   const QString _dbHost;
   const quint16 _dbPort;
   const QString _userName;
   const QString _userPassword;
   QMap<QThread*, QString> _threadConnectionNameMap;
   QMutex _mutex;
};
 

ConnectionPool.cpp
Код
C++ (Qt)
#include "ConnectionPool.h"
#include <QDataStream>
#include <QCryptographicHash>
 
 
ConnectionPool::ConnectionPool(const QString driverName,
                              const QString& dbName,
                              const QString& dbHost,
                              const quint16& dbPort,
                              const QString& userName,
                              const QString& userPassword,
                              QObject* parent)
   : QObject(parent),
     _driverName(driverName),
     _dbName(dbName),
     _dbHost(dbHost),
     _dbPort(dbPort),
     _userName(userName),
     _userPassword(userPassword)
{
}
 
ConnectionPool::~ConnectionPool()
{
   foreach (QThread* thread, _threadConnectionNameMap.keys())
   {
       QSqlDatabase::removeDatabase(_threadConnectionNameMap.value(thread));
   }
}
 
QSqlDatabase ConnectionPool::getConnection()
{
   return getConnectionForThread(QThread::currentThread());
}
 
QSqlDatabase ConnectionPool::getConnectionForThread(QThread* thread)
{
   QSqlDatabase db;
   if(_threadConnectionNameMap.contains(thread))
   {
       db = QSqlDatabase::database(_threadConnectionNameMap.value(thread));
   }
   else
   {
       QMutexLocker locker(&_mutex);
       const QString connectionName = generateConnectionName(thread);
       db = QSqlDatabase::addDatabase(_driverName, connectionName);
       _threadConnectionNameMap.insert(thread, connectionName);
       connect(thread, &QThread::destroyed, this, &ConnectionPool::onThreadDestroyed);
 
       db.setDatabaseName(_dbName);
       db.setUserName(_userName);
       db.setPassword(_userPassword);
       db.setHostName(_dbHost);
       db.setPort(_dbPort);
   }
 
   if(!db.isOpen()) db.open();
   return db;
}
 
QString ConnectionPool::generateConnectionName(QThread* thread) const
{
   QByteArray data;
   QDataStream stream(&data, QIODevice::WriteOnly);
   stream.setVersion(QDataStream::Qt_5_0);
 
   stream << (long)thread;
   stream << _driverName;
   stream << _dbName;
   stream << _dbHost;
   stream << _dbPort;
   stream << _userName;
   stream << _userPassword;
 
   QCryptographicHash hash(QCryptographicHash::Md5);
   hash.addData(data);
 
   return QString(hash.result().toHex());
}
 
void ConnectionPool::onThreadDestroyed()
{
   QThread* thread = static_cast<QThread*>(sender());
   if(_threadConnectionNameMap.contains(thread))
   {
       QString connectionName = _threadConnectionNameMap.take(thread);
       QSqlDatabase::removeDatabase(connectionName);
   }
}
 
 


Название: Re: connection pool плюсы и минусы
Отправлено: Old от Март 30, 2016, 10:28
Бегло просмотрел, что сразу бросилось в глаза.
В методе getConnectionForThread вы защищаете мьютексом только добавление нового соединения, хотя нужно и поиск с извлечением защищать. Для этого может пригодиться QReadWriteLock.

Теперь вообще по концепту. Я сразу создаю пул рабочих потоков и каждому потоку сразу создаю соединение. Потоки и соединения живут весь период жизни пула.


Название: Re: connection pool плюсы и минусы
Отправлено: unkeep от Март 30, 2016, 11:19
с учётом замечаний по синхронизации
Код
C++ (Qt)
QSqlDatabase ConnectionPool::getConnectionForThread(QThread* thread)
{
   QReadLocker rLocker(&_rwLock);
   if(_threadConnectionNameMap.contains(thread))
   {
       QSqlDatabase db = QSqlDatabase::database(_threadConnectionNameMap.value(thread));
       db.open();
       return db;
   }
   rLocker.unlock();
 
   QWriteLocker wLocker(&_rwLock);
   const QString connectionName = generateConnectionName(thread);
   QSqlDatabase db = QSqlDatabase::addDatabase(_driverName, connectionName);
   _threadConnectionNameMap.insert(thread, connectionName);
   connect(thread, &QThread::destroyed, this, &ConnectionPool::onThreadDestroyed);
   db.setDatabaseName(_dbName);
   db.setUserName(_userName);
   db.setPassword(_userPassword);
   db.setHostName(_dbHost);
   db.setPort(_dbPort);
   if(!db.isOpen()) db.open();
   return db;
}
 

А концепции у нас разнятся, так как в текущей задаче используется пул потоков. Чаще всего вызывается getConnection() в методе, вызванном с помощью QtConcurrentRun.


Название: Re: connection pool плюсы и минусы
Отправлено: Old от Март 30, 2016, 12:01
Да, QReadWriteLock, в отличие от бустовских аналогов, не позволяет изменять роль, т.е. стать из читателя писателем. Я его посоветовал, но сейчас забираю свой совет обратно, по крайней мере пока его не доработают. :)
Лучше обойтись простым мьютексом.

Код
C++ (Qt)
QSqlDatabase ConnectionPool::getConnectionForThread(QThread* thread)
{
   QMutexLocker locker(&_mutex);
   if(_threadConnectionNameMap.contains(thread))
   {
       QSqlDatabase db = QSqlDatabase::database(_threadConnectionNameMap.value(thread));
       db.open();
       return db;
   }
 
   const QString connectionName = generateConnectionName(thread);
   QSqlDatabase db = QSqlDatabase::addDatabase(_driverName, connectionName);
   _threadConnectionNameMap.insert(thread, connectionName);
   connect(thread, &QThread::destroyed, this, &ConnectionPool::onThreadDestroyed);
   db.setDatabaseName(_dbName);
   db.setUserName(_userName);
   db.setPassword(_userPassword);
   db.setHostName(_dbHost);
   db.setPort(_dbPort);
   if(!db.isOpen()) db.open();
   return db;
}
 


Название: Re: connection pool плюсы и минусы
Отправлено: ballard от Декабрь 14, 2016, 18:36
Теперь вообще по концепту. Я сразу создаю пул рабочих потоков и каждому потоку сразу создаю соединение. Потоки и соединения живут весь период жизни пула.

можно посмотреть код такой реализации?


Название: Re: connection pool плюсы и минусы
Отправлено: ballard от Декабрь 20, 2016, 14:23
Бегло просмотрел, что сразу бросилось в глаза.
В методе getConnectionForThread вы защищаете мьютексом только добавление нового соединения, хотя нужно и поиск с извлечением защищать. Для этого может пригодиться QReadWriteLock.

Теперь вообще по концепту. Я сразу создаю пул рабочих потоков и каждому потоку сразу создаю соединение. Потоки и соединения живут весь период жизни пула.

на самом деле интересует организация много-поточного доступа к коннекшенам, я пробовал делать воркеры, наследуемые от QThread, но тогда непонятно как сделать метод запроса с возвращаемым результатом, так же пробовал выполнять запросы в QtConcurrent, но тут проблема в том, что соединение созданное при помощи QSqlDatabase::addDatabase должно вызываться из того же потока в котором создано, т.е. хотя бы архитектура "на пальцах" или в псевдокоде, если не хочется раскрывать свое исполнение


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 20, 2016, 15:09
Для каждой нитки можно клонировать подключения.
Таких клонов может быть столько, сколько есть рабочих ниток. В каждой нитке свое подключение.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 20, 2016, 23:35
Для каждой нитки можно клонировать подключения.
Таких клонов может быть столько, сколько есть рабочих ниток. В каждой нитке свое подключение.

Большое спасибо за ответ!

А можно показать в виде кода, о чем речь (хотя бы частично)?

У меня пока такая конфигурация (если можно, код ревью, критику и т.д.  :) ):

Пул создает набор воркеров (вместо clone делает addDatabase с именем, хотя в документации сказано, что в этом случае нужно обращаться к БД из того потока к котором вызывался addDatabase, но пока сбоев не было, может здесь что-то изменилось) и просто по очереди передает им запросы (в дальнейшем есть мысли прикрутить сюда что-то типа consumer-producer), в принципе работает при обращении к пулу из разных потоков и без QMutex, но на всякий случай его пока оставил.

Хочется создать набор соединений и выполнять к нему запросы вида QSqlQuery executeQuery(const QString &query), причем накидать сразу много запросов, чтобы они по очереди выполнились... и, вроде бы, все работает, но возможно есть какие-то проверенные временом типовые решения, или в дальшейшем я встречу какие-то проблемы.

Просто очень заинтересовала упомянутая реализация концепта, именно к такой и стремлюсь.

p.s. вроде нашел проблему, с вызовами из разных потоков непонятная ситуация

Пул:
pool.h
Код:
class DBConnectionPool : public QObject
{
    Q_OBJECT
public:
    explicit DBConnectionPool(QObject *parent = 0);
    DBConnectionPool(const PoolConfig& config, QObject* parent = 0);
    void start();
    void stop();
    QSqlQuery executeQuery(const QString &query);

signals:
    void stateChanged( const QString& msg );

private:
    PoolConfig config;
    QList<DatabaseWorker*> databaseConnections;
    QSqlQuery execute(const QString& statement);
    QFuture<QSqlQuery> future;
    int workerCounter = 0;
    QMutex mutex;
};

pool.cpp
Код:
DBConnectionPool::DBConnectionPool(const PoolConfig &config, QObject *parent) : QObject(parent)
{
    this->config = config;
}

void DBConnectionPool::start()
{
    for(int i = 0; i < this->config.connectionsCount; i++) {
        DatabaseWorker* worker = new DatabaseWorker;
        worker->config.hostName = this->config.hostName;
        worker->config.databaseName = this->config.databaseName;
        worker->config.userName = this->config.userName;
        worker->config.password = this->config.password;
        worker->config.databaseReferenceName = "dbWorker@" + QString::number(i);
        worker->init();
        worker->createConnection();
        databaseConnections << worker;
    }
}

QSqlQuery DBConnectionPool::execute(const QString& statement)
{
    QMutexLocker locker(&mutex);
    QSqlQuery query = this->databaseConnections[this->workerCounter]->executeSync(statement);
    if (this->workerCounter < this->config.connectionsCount - 1) {
        this->workerCounter++;
    } else {
        this->workerCounter = 0;
    }
    return query;
}

QSqlQuery DBConnectionPool::executeQuery(const QString& statement)
{
    future = QtConcurrent::run(this, &DBConnectionPool::execute, statement);
    future.waitForFinished();
    return(future.result());
}

Воркер:
worker.h
Код:
class DatabaseWorker : public QObject
{
    Q_OBJECT

private:
    QSqlDatabase db;
    QSqlQuery executeRequest(const QString& request);
    QFuture<QSqlQuery> future;

public:
    explicit DatabaseWorker(QObject *parent = 0);
    void init();
    bool createConnection();
    QSqlQuery executeSync(const QString& request);
    void executeAsync();
    bool isBusy();
    PoolConfig config;
    QStringList requestList;
signals:

public slots:
};

worker.cpp
Код:
void DatabaseWorker::init()
{
    db = QSqlDatabase::addDatabase("QPSQL", this->config.databaseReferenceName);
}

bool DatabaseWorker::createConnection()
{
    db.setHostName(this->config.hostName);
    db.setDatabaseName(this->config.databaseName);
    db.setUserName(this->config.userName);
    db.setPassword(this->config.password);
    db.open();
    if (!db.isOpen()) {
        qDebug() << "Ошибка при подключении к СУБД: " << db.lastError() << endl;
        return false;
    }
    return true;
}

QSqlQuery DatabaseWorker::executeRequest(const QString& request)
{
    QSqlQuery query(db);
    bool result = query.exec(request);
    if (!result) {
        qDebug() << "Ошибка при выполнении запроса: " <<db.lastError() << endl;
    }
    return query;
}

QSqlQuery DatabaseWorker::executeSync(const QString& request)
{
    future = QtConcurrent::run(this, &DatabaseWorker::executeRequest, request);
    future.waitForFinished();
    return(future.result());
}


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 23, 2016, 11:56

есть ли какие-нибудь замечания/советы по реализации? (очень хотелось бы услышать) :)


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 23, 2016, 21:25
есть ли какие-нибудь замечания/советы по реализации? (очень хотелось бы услышать) :)
А у вас этот код нормально работает?
Как-то для меня не очень понятен запуск функции в отдельном потоке и блокировка вызывающего потока? Если вы его все равно блокируете, так в нем лучше и выполняйте функцию?

Но вы все усложняете, достаточно запустить воркеры и организовать для них одну очередь запросов, по мере освобождения воркеры будут брать запросы из очереди или заснут, пока не появятся новые запросы.
Организация таких очередей 100500 раз обсуждалась на форуме, поищите, там и примеры были.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 23, 2016, 21:32
есть ли какие-нибудь замечания/советы по реализации? (очень хотелось бы услышать) :)
А у вас этот код нормально работает?
Как-то для меня не очень понятен запуск функции в отдельном потоке и блокировка вызывающего потока? Если вы его все равно блокируете, так в нем лучше и выполняйте функцию?

Но вы все усложняете, достаточно запустить воркеры и организовать для них одну очередь запросов, по мере освобождения воркеры будут брать запросы из очереди или заснут, пока не появятся новые запросы.
Организация таких очередей 100500 раз обсуждалась на форуме, поищите, там и примеры были.


Спасибо Вам! Посмотрю конечно в поиске про очереди, т.к. именно так как выделено я и хочу сделать, но просто гуглопоиск на эту тему ни к чему не привел...поэтому и обратился :) если вдруг вспомните ссылки, подскажите про них) Я смотрел примеры qt waiting condition и semaphore, думал копать в эту сторону) ну и как я уже писал, за любые примеры, даже псевдокодом буду очень признателен :)


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 23, 2016, 21:45
http://www.prog.org.ru/topic_23820_0.html
http://www.prog.org.ru/topic_27817_0.html
http://www.prog.org.ru/topic_29697_0.html
http://www.prog.org.ru/topic_14426_0.html
http://www.prog.org.ru/topic_12552_0.html


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 24, 2016, 00:04

Еще раз спасибо! на самом деле многое обсуждалось) проштудирую ссылки и выложу свое обновленное решение :)


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 25, 2016, 02:03
Но вы все усложняете, достаточно запустить воркеры и организовать для них одну очередь запросов, по мере освобождения воркеры будут брать запросы из очереди или заснут, пока не появятся новые запросы.
Организация таких очередей 100500 раз обсуждалась на форуме, поищите, там и примеры были.

Просмотрел ссылки, а так же проглядел раздел про многопоточности, но так и не получается сформировать цельное представление...
Как лучше организовать очередь? через semaphore? или через qqueue...
Как быть с воркерами? qtconcurrent + future как мне казалось подходят потому что позволяют организовать синхронную блокирующую функцию типа QSqlQuery executeQuery (const QString& query) {...} , если делать воркеры через qthread, например, то возврат результата можно реализовать только через сигналы-слоты...это не то, что нужно, хотя может быть я и ошибаюсь
Идеально было бы реализовать вариант с забором запросов из очереди и засыпанием, но здесь уже, вроде бы надо использовать qwaitcondition?
как-то разные части задачи делаются разными способами, а как их совместить пока не вижу...


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 25, 2016, 02:27
Как лучше организовать очередь? через semaphore? или через qqueue...
QList+QWaitCondition

как-то разные части задачи делаются разными способами, а как их совместить пока не вижу...
А на что там остается смотреть? :)
Вот простейший пример очереди задач: http://www.prog.org.ru/index.php?topic=14426.msg95463#msg95463
Клиент(ы) в нее добавляют задания, а ворер(ы) достают и обрабатывают. Результат отдают через сигнал. Все это добро (очередь и нитки-воркеры) прячется за классом пул, в который можно добавлять запросы, а при получении ответа он эмитит сигнал.
Если нужны еще и синхронные запросы, то это делается не через воркеры, а синхронным запросом через отдельное подключение.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 25, 2016, 21:27
QList+QWaitCondition
А на что там остается смотреть? :)
Вот простейший пример очереди задач: http://www.prog.org.ru/index.php?topic=14426.msg95463#msg95463
Клиент(ы) в нее добавляют задания, а ворер(ы) достают и обрабатывают. Результат отдают через сигнал. Все это добро (очередь и нитки-воркеры) прячется за классом пул, в который можно добавлять запросы, а при получении ответа он эмитит сигнал.
Если нужны еще и синхронные запросы, то это делается не через воркеры, а синхронным запросом через отдельное подключение.

Спасибо! Т.е. я правильно понял, что либо пул, либо синхронный запрос?


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 25, 2016, 21:32
Спасибо! Т.е. я правильно понял, что либо пул, либо синхронный запрос?
Скорее, либо другие нити, либо синхронный запрос.
Синхронный запрос подразумевает, что вы запускаете выполнение этого запроса в текущей нитке и ожидаете его завершения.
Параллельные потоки могут выполнять несколько запросов одновременно.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 25, 2016, 21:40
Скорее, либо другие нити, либо синхронный запрос.
Синхронный запрос подразумевает, что вы запускаете выполнение этого запроса в текущей нитке и ожидаете его завершения.
Параллельные потоки могут выполнять несколько запросов одновременно.

В принципе, наверное, можно передавать какой-то идентификатор в запрос, чтобы вернуть его через сигнал, для определения, кому он предназначен...


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 26, 2016, 10:18
Скорее, либо другие нити, либо синхронный запрос.

Можете еще вот эту фразу пояснить?


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 26, 2016, 10:27
Можете еще вот эту фразу пояснить?
Пул просто более высокоуровневое понятие, чем нить.
Для синхронного запроса уже нить избыточна, а пул нитей и подавно.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 26, 2016, 10:30
Пул просто более высокоуровневое понятие, чем нить.
Для синхронного запроса уже нить избыточна, а пул нитей и подавно.

Так вот и вопрос, есть ли способ организовать доступ выполнение синхронных запросов из разных потоков при помощи одного коннекшена, либо их набора с очередью для запросов...?


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 26, 2016, 10:36
Так вот и вопрос, есть ли способ организовать доступ выполнение синхронных запросов из разных потоков при помощи одного коннекшена, либо их набора с очередью для запросов...?
Сформулируйте, что понимаете под синхронными запросами.
Вы хотите выполнять серию запросов в определенном порядке? Тогда самое просто изменить ваш пул так, что бы он принимал не строку запроса, а массив строк (QStringList). А нитки-воркеры в цикле их выполняли.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 26, 2016, 10:44
Сформулируйте, что понимаете под синхронными запросами.
Вы хотите выполнять серию запросов в определенном порядке? Тогда самое просто изменить ваш пул так, что бы он принимал не строку запроса, а массив строк (QStringList). А нитки-воркеры в цикле их выполняли.

Я хочу сделать так, чтобы у моего пула, хотя это скорее уже не пул, а что-то большее, но не суть, был один публичный метод, типа:

Код:
QSqlQuery executeQuery(const QString &query);

который бы располагал такие запросы в очереди, а свободные воркеры их из нее забирали и обрабатывали.

При этом мне нужна возможность обращаться к пулу из разных потоков.

Я, в принципе, понимаю, как это сделать через сигнал-слот, но можно ли сделать метод возвращающий результаты запроса в таком виде пока не пойму...


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 26, 2016, 11:05
который бы располагал такие запросы в очереди, а свободные воркеры их из нее забирали и обрабатывали.
При этом мне нужна возможность обращаться к пулу из разных потоков.
Ну так мы сейчас именно это и обсуждаем. Для этого нужны несколько рабочих ниток и очередь запросов (QList+QWaitCondition).
Все.

Я, в принципе, понимаю, как это сделать через сигнал-слот, но можно ли сделать метод возвращающий результаты запроса в таком виде пока не пойму...
А результат можно возвращать с помощью сигнала, добавив каждому запросу некий ID.
Код
C++ (Qt)
public:
   ID    executeQuery( const QString &query );    // Возвращает ID запроса
 
signals:
   void    queryFinished( ID id, const QSqlQuery &result );    // Испускается после выполнения запроса
 


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 26, 2016, 11:58
Ну так мы сейчас именно это и обсуждаем. Для этого нужны несколько рабочих ниток и очередь запросов (QList+QWaitCondition).
Все.

А результат можно возвращать с помощью сигнала, добавив каждому запросу некий ID.
Код
C++ (Qt)
public:
   ID    executeQuery( const QString &query );    // Возвращает ID запроса
 
signals:
   void    queryFinished( ID id, const QSqlQuery &result );    // Испускается после выполнения запроса
 


Сейчас еще такая мысль есть, а что если мне в QList заданий для воркеров класть QFuture, и пусть у меня при вызове из одного потока запрос будет блокирующий, т.е. пока текущий не завершится следующий не поступит на исполнение? а из других потоков задания будут проходить


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 26, 2016, 12:06
Если вы блокируете текущую нить на время выполнения запроса, то не понятно для чего его выполнять в другой нитке. Ну и выполняйте этот запрос сразу в текущей?  ???


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 26, 2016, 12:09
Если вы блокируете текущую нить на время выполнения запроса, то не понятно для чего его выполнять в другой нитке. Ну и выполняйте этот запрос сразу в текущей?  ???

Изначально суть в том, как вы понимаете, чтобы открыть и держать открытыми несколько соединений с БД.
Если я блокирую какую-то одну нитку, то остальные остаются свободны для обращения из других потоков.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 26, 2016, 12:23
Изначально суть в том, как вы понимаете, чтобы открыть и держать открытыми несколько соединений с БД.
Если я блокирую какую-то одну нитку, то остальные остаются свободны для обращения из других потоков.
Так откройте сразу 10 соединений и используйте их. Зачем вам рабочие нитки?


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 26, 2016, 12:30
Так откройте сразу 10 соединений и используйте их. Зачем вам рабочие нитки?

Всмысле открывать сразу в вызывающих потоках? или вы что имеете в виду?
Идея такая, что у меня может быть к примеру 20 вызывающих при 5 открытых нитках в пуле, но каждых из этих 20-ти будет класть по задания последовательно, после выполнения предыдущего


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 26, 2016, 12:43
Всмысле открывать сразу в вызывающих потоках? или вы что имеете в виду?
Идея такая, что у меня может быть к примеру 20 вызывающих при 5 открытых нитках в пуле, но каждых из этих 20-ти будет класть по задания последовательно, после выполнения предыдущего
Пожалуйста, пулу все равно, дожидается ли клиент отработки предудущего запроса или нет.
Нужно ждать хорошо, не нужно - тоже хорошо.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 26, 2016, 13:47
Пожалуйста, пулу все равно, дожидается ли клиент отработки предудущего запроса или нет.
Нужно ждать хорошо, не нужно - тоже хорошо.

Ну да, но получается мне нужно как раз организовать промежуточный класс между вызывающими потоками и пулом, у которого и будет нужный мне метод, возвращающий QSqlRequest, Вы это имеете в виду?


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: Old от Декабрь 26, 2016, 13:53
Ну да, но получается мне нужно как раз организовать промежуточный класс между вызывающими потоками и пулом, у которого и будет нужный мне метод, возвращающий QSqlRequest, Вы это имеете в виду?
Да, он будет запускать нитки-воркеры и нагружать их работой (через очередь), в ответ сигналя о результатах. Как у вас и было реализовано.
Добавьте в него очередь и все.


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 26, 2016, 13:55
Да, он будет запускать нитки-воркеры и нагружать их работой (через очередь), в ответ сигналя о результатах. Как у вас и было реализовано.
Добавьте в него очередь и все.

Ok, спасибо, буду делать, потом поделюсь результатом :)


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 27, 2016, 00:34

В общем что-то нет прогресса с очередью :-\

Только сделал так выполнение запроса (QueryWrapper - это обернутый QSqlRequest):
Код
C++ (Qt)
QueryWrapper DBConnectionPool::execute(const QString &statement, const QMap<QString, QVariant> &values)
{
   QMutexLocker locker(&mutex);
   bool executed = false;
   QueryWrapper wrapper;
   while (!executed) {
       for (auto& connection : databaseConnections) {
           if (!connection->isBusy()) {
               wrapper = connection->executeSync(statement, values);
               executed = true;
               break;
           }
       }
   }
   return wrapper;
}
 
QueryWrapper DBConnectionPool::executeQuery(const QString &statement, const QMap<QString, QVariant> &values)
{
   wrappedFuture = QtConcurrent::run(this, &DBConnectionPool::execute, statement, values);
   wrappedFuture.waitForFinished();
   return(wrappedFuture.result());
}

Очередь непонятно, как сюда прикрутить: т.к., например future создается через qtconcurrent::run, который сразу исполняет запрос и, соответственно, в QList для некого воркера future не добавить.

Т.е. для синхронного запроса с очередью мне нужно что-то типа:
Код
C++ (Qt)
QueryWrapper DBConnectionPool::executeQuery(const QString &statement, const QMap<QString, QVariant> &values)
{
   Task task(statement, values);
   taskList.append(task);
   task.waitForResult();
   return task.result();
}

taskList это как раз qlist из которого некий consumer заберет запрос и выполнит

Пока что не понимаю, можно ли это сделать, и, если да, то как ???


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 27, 2016, 14:08
Да, он будет запускать нитки-воркеры и нагружать их работой (через очередь), в ответ сигналя о результатах. Как у вас и было реализовано.
Добавьте в него очередь и все.

В общем-то пришла идея сделать эту функцию блокирующей, и тогда можно встроить работу с очередью. Либо по таймауту, либо цикл с проверкой выполнения запроса, либо через Qt::BlockingQueuedConnection, как Вы думаете, какой вариант лучше?

Код
C++ (Qt)
QueryWrapper DBConnectionPool::executeQuery(const QString &statement, const QMap<QString, QVariant> &values)
{
   Task task(statement, values);
   taskList.append(task);
 
   // block
 
   return task.result();
}


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 27, 2016, 23:43

В общем сделал пока вариант с одним воркером...из одного потока пока все работает.
Блокирующая функция выполняется. Можете посмотреть момент с ::run() у QueryThread и по Tasking, все ли верно?

worker.h
Код
C++ (Qt)
class DatabaseWorker : public QObject
{
   Q_OBJECT
public:
   explicit DatabaseWorker(QObject* parent = 0);
   DatabaseWorker(const QString& threadId, QObject* parent = 0);
   void init(const QString& threadId);
 
private:
   QSqlDatabase m_database;    
 
signals:
   void results( const QList<QSqlRecord>& records );
 
public slots:
   void slotExecute( const QString& query );
};

worker.cpp
Код
C++ (Qt)
DatabaseWorker::DatabaseWorker(const QString& threadId, QObject* parent ) : QObject(parent)
{
   init(threadId);
}
 
void DatabaseWorker::init(const QString& threadId)
{
   QString dbThreadId;
   QString emptyStr = "";
   if (threadId == emptyStr) {
       dbThreadId = QString::number((qintptr)QThread::currentThreadId());
   } else {
       dbThreadId = threadId;
   }
   m_database = QSqlDatabase::addDatabase( "QPSQL", dbThreadId );
   m_database.setDatabaseName( "test" );
   m_database.setHostName( "localhost" );
   m_database.setUserName( "postgres" );
   m_database.setPassword( "postgres" );
   if ( !m_database.open() )
   {
       return;
   }
}
 
void DatabaseWorker::slotExecute( const QString& query )
{
   QList<QSqlRecord> recs;
   QSqlQuery sql( query, m_database );
   while ( sql.next() )
   {
       recs.push_back( sql.record() );
   }
   emit results( recs );
}

querythread.h
Код
C++ (Qt)
class QueryThread : public QThread
{
   Q_OBJECT
public:
   explicit QueryThread(QThread* parent = 0);
   QueryThread(Tasking* taskManager, QThread* parent = 0);
   void execute( const QString& query );
   ~QueryThread();
   void prepare();
 
protected:
   void run();
 
private:
   Tasking* taskManager;
   DatabaseWorker* m_worker;    
 
signals:
   void progress( const QString& msg );
   void ready(bool);
   void results( const QList<QSqlRecord>& records );
   void executefwd( const QString& query );
 
public slots:
   void getResults( const QList<QSqlRecord>& records );
};

querythread.cpp
Код
C++ (Qt)
QueryThread::QueryThread(Tasking* taskManager, QThread *parent) : QThread(parent)
{
   this->taskManager = taskManager;
 
}
 
QueryThread::~QueryThread()
{
   delete m_worker;
}
 
void QueryThread::prepare()
{
   emit ready(false);
   m_worker = new DatabaseWorker;
   connect( this, &QueryThread::executefwd, m_worker, &DatabaseWorker::slotExecute );
   qRegisterMetaType< QList<QSqlRecord> >( "QList<QSqlRecord>" );
   connect( m_worker, &DatabaseWorker::results, this, &QueryThread::getResults );
   emit ready(true);
}
 
void QueryThread::execute( const QString& query )
{
   emit executefwd( query );
}
 
void QueryThread::getResults( const QList<QSqlRecord>& records )
{
   emit results( records );    
}
 
void QueryThread::run()
{
   forever {
       execute(taskManager->getTask());
   }
}

tasking.h
Код
C++ (Qt)
class Tasking : public QObject
{
   Q_OBJECT
 
public:
   explicit Tasking(QObject *parent = 0);
   QMutex mutex;
   QWaitCondition cond;
   QString getTask();
   void addTask(const QString &task);
   QList<QString> taskList;
 
signals:
 
public slots:
};

tasking.cpp
Код
C++ (Qt)
void Tasking::addTask(const QString &task)
{
   QMutexLocker locker( &mutex );
   taskList.append( task );
   cond.wakeAll();
}
 
QString Tasking::getTask()
{
   QMutexLocker locker( &mutex );
 
   while( taskList.empty() ) {
       cond.wait( &mutex );
   }
 
   return taskList.takeFirst();
}

pool.h
Код
C++ (Qt)
class WorkerPool : public QObject
{
   Q_OBJECT
public:
   explicit WorkerPool(QObject *parent = 0);
   QList<QSqlRecord> executeRequest( const QString& request );
 
private:
   QueryThread *querythread;
 
   bool isResultsReady = false;
   QList<QSqlRecord> recs;
   void saveResults(const QList<QSqlRecord> &recs);
 
   Tasking* taskManager;
 
 
signals:
   void requestDone();

pool.cpp
Код
C++ (Qt)
WorkerPool::WorkerPool(QObject *parent) : QObject(parent)
{
   taskManager = new Tasking;
   querythread = new QueryThread(taskManager);
   connect(querythread, &QueryThread::results, this, &WorkerPool::saveResults );
   querythread->prepare();
   querythread->start();
}
 
QList<QSqlRecord> WorkerPool::executeRequest(const QString &request)
{
   isResultsReady = false;
   taskManager->addTask(request);
 
   QEventLoop loop;
   connect(this, &WorkerPool::requestDone, &loop, &QEventLoop::quit);
   loop.exec();
 
   return recs;
}
 
void WorkerPool::saveResults(const QList<QSqlRecord> &recs)
{
   this->recs = recs;
   isResultsReady = true;
   emit requestDone();
}


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 27, 2016, 23:59
Теперь надо еще продумать, как идентифицировать сигналы от разных воркеров, при обращении из разных потоков


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 28, 2016, 16:32
А результат можно возвращать с помощью сигнала, добавив каждому запросу некий ID.
Код
C++ (Qt)
public:
   ID    executeQuery( const QString &query );    // Возвращает ID запроса
 
signals:
   void    queryFinished( ID id, const QSqlQuery &result );    // Испускается после выполнения запроса
 

Обновил пул для работы с несколькими воркерами, можете посоветовать, как вернуть данные именно от того воркера, которому отправлен запрос?

Код
C++ (Qt)
WorkerPool::WorkerPool(const int &workersCount, QObject *parent) : QObject(parent)
{
   taskManager = new Tasking;
   for (auto i=0; i<workersCount; i++) {
       QueryThread* thread = new QueryThread(taskManager, "worker@" + QString::number(i));
       connect(thread, &QueryThread::results, this, &WorkerPool::saveResults);
       workersList << thread;
   }
 
   foreach (QueryThread* worker, workersList) {
       worker->prepare();
       worker->start();
   }
}
 
QList<QSqlRecord> WorkerPool::executeRequest(const QString &request)
{
   isResultsReady = false;
   taskManager->addTask(request);
 
   QEventLoop loop;
   connect(this, &WorkerPool::requestDone, &loop, &QEventLoop::quit);
   loop.exec();
 
   return recs;
}
 
void WorkerPool::saveResults(const QList<QSqlRecord> &recs)
{
   this->recs = recs;
   isResultsReady = true;
   emit requestDone();
}


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 28, 2016, 17:17
В общем добавил пока сортрировку следующим образом, записывая запрос , блокируя мьютексом и проверяя потом на совпадение, но с таким способом пул зависает через несколько запросов, которые производятся из разных потоков, похоже из-за мьютекса возникает deadlock, без блокирования мьютексом запросы идут нормально, но, естественно порядок не совпадает...что тут можно сделать? :

Код
C++ (Qt)
WorkerPool::WorkerPool(const int &workersCount, QObject *parent) : QObject(parent)
{
   taskManager = new Tasking;
   for (auto i=0; i<workersCount; i++) {
       QueryThread* thread = new QueryThread(taskManager, "worker@" + QString::number(i));
       connect(thread, &QueryThread::results, this, &WorkerPool::saveResults);
       workersList << thread;
   }
 
   foreach (QueryThread* worker, workersList) {
       worker->prepare();
       worker->start();
   }
}
 
QList<QSqlRecord> WorkerPool::executeRequest(const QString &request)
{
   isResultsReady = false;
   taskManager->addTask(request);
 
   QMutexLocker locker(&mutex);
   lastRequest = request;
 
   QEventLoop loop;
   connect(this, &WorkerPool::requestDone, &loop, &QEventLoop::quit);
   loop.exec();
 
   return recs;
}
 
void WorkerPool::saveResults(const QList<QSqlRecord> &recs, const QString &request)
{
   if (request == lastRequest) {
       this->recs = recs;
       isResultsReady = true;
       emit requestDone();
   }
}


Название: Re: [РЕШЕНО] connection pool плюсы и минусы
Отправлено: ballard от Декабрь 28, 2016, 21:10
все, вроде бы победил: сделал мапу с результатами :)
Код
C++ (Qt)
QList<QSqlRecord> WorkerPool::executeRequest(const QString &request)
{
   isResultsReady = false;
 
   QMutexLocker locker(&mutex);
   taskManager->addTask(request);
 
   bool completed = false;
 
   while (!completed) {
       if (resultsMap.contains(request)) {
           completed = true;
       }
   }
   return resultsMap.take(request);
}
 
void WorkerPool::saveResults(const QList<QSqlRecord> &recs, const QString &request)
{
   resultsMap[request] = recs;
   isResultsReady = true;
   emit requestDone();
}