Использование соединения QSqlDatabase в QtConcurrent :: run (пул псевдо-соединений) - PullRequest
3 голосов
/ 16 октября 2011

Я пытаюсь найти эффективный способ обработки некоторых запросов к базе данных в Qt. Сценарий по сути состоит в том, что у нас есть много событий, которые нам нужно записать в БД. Мы не можем блокировать основной поток во время записи, поэтому эти записи выполняются в отдельных потоках с использованием QtConcurrent::run.

Теперь проблема в том, что в настоящее время каждый параллельный запуск требует создания нового соединения с БД. Мы хотели бы просто иметь возможность создать соединение один раз и использовать его повторно, но в документации Qt соединение может использоваться только в потоке, который его создал. Использование QtConcurrent делает это довольно проблематичным, поскольку мы не знаем, в каком потоке мы будем работать.

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

Есть ли способ использовать одно соединение с БД и при этом использовать QtConcurrent? Или мы, как я боюсь, должны использовать QThread и реализовать собственную сигнализацию, а не использовать параллельную среду?


Ответ : Ответы, как мне кажется, указывают на то, что это просто невозможно. Соединения QtConcurrent и DB не очень хорошо работают вместе. Это действительно очень плохо. Думаю, я просто вернусь к созданию собственного потока и использованию пользовательских сигналов и слотов для общения.

Ответы [ 4 ]

0 голосов
/ 02 декабря 2017

Мое решение - использовать QtConcurrent с пользовательским пулом потоков, который не уничтожает его потоки. И в контексте каждого потока я создаю выделенное QSqlDatabase соединение с именем этого потока в качестве имени соединения, так что каждый поток получает одно и то же соединение каждый раз, когда ему нужно общаться с базой данных.

Установка:

mThreadPool = new QThreadPool(this);
// keep threads indefinitely so we don't loose QSqlDatabase connections:
mThreadPool->setExpiryTimeout(-1);
mThreadPool->setMaxThreadCount(10); /* equivalent to 10 connections */

qDebug() << "Maximum connection count is "
         << mThreadPool->maxThreadCount();

Destructor:

// remove the runnables that are not yet started
mThreadPool->clear();
// wait for running threads to finish (blocks)
delete mThreadPool;

Пример реализации API, возвращающий будущее, которое можно использовать для получения данных из базы данных, когда они станут доступны:

QFuture<QList<ArticleCategory> *> 
DatabaseService::fetchAllArticleCategories() const
{
    return QtConcurrent::run(mThreadPool, 
&DatabaseService::fetchAllArticleCategoriesWorker, mConnParameters);
}

Обратите внимание, что мое решение не управляет объектами, которые оно создает. Вызывающий код должен управлять этой памятью (QList, возвращенный выше).

Рабочая функция сопутствующего потока:

QList<ArticleCategory> *DatabaseService::fetchAllArticleCategoriesWorker(const DatabaseConnectionParameters &dbconparams)
{
    try {
        setupThread(dbconparams);
    } catch (exceptions::DatabaseServiceGeneralException &e) {
        qDebug() << e.getMessage();
        return nullptr;
    }

    QString threadName = QThread::currentThread()->objectName();
    QSqlDatabase db    = QSqlDatabase::database(threadName, false);

    if (db.isValid() && db.open()) {
        QSqlQuery q(db);
        q.setForwardOnly(true);
        // ...
    }
// else return nullptr
// ...
}

Если вы заметили, setupThread всегда вызывается в начале рабочего процесса, который в основном подготавливает соединение с базой данных для вызывающего потока:

void DatabaseService::setupThread(const DatabaseConnectionParameters &connParams)
{
    utilities::initializeThreadName(); // just sets a QObject name for this thread

    auto thisThreadsName = QThread::currentThread()->objectName();

    // check if this thread already has a connection to a database:
    if (!QSqlDatabase::contains(thisThreadsName)) {
        if (!utilities::openDatabaseConnection(thisThreadsName, connParams))
        {
            qDebug() << "Thread"
                    << thisThreadsName
                    << "could not create database connection:"
                    << QSqlDatabase::database(thisThreadsName, false).lastError().text();
        }
        else
        {
            qDebug() << "Thread"
                    << thisThreadsName
                    << "successfully created a database connection.";
        }
    }
}
0 голосов
/ 17 октября 2011

Согласно документации Qt это невозможно. QtConcurrent :: run () берет поток из пула потоков, поэтому вы не знаете, какой поток будет использоваться каждый раз. Я не способ справиться с этим. Это займет первую доступную тему.

Я действительно думаю, что вы не должны использовать QtConcurrent :: run () в этой ситуации. Хороший способ, который я мог бы подумать, это использовать QThread с QEventLoop. Это очень просто: вы просто создаете переопределение QThread, вызывая exec () немедленно. Примерно так:

class MyEventLoop : public QThread
{
public:
   MyEventLoop() {
      db = QSqlDatbase::addDatabase(<connection_name>);
      // ...
   }

   ~MyEventLoop() {
      QSqlDatabase::removeDatabase(<connection_name>);
      // ...
   }

   bool event(QEvent* event)
   {
      qDebug("Processing event.");
      return true;
   }

   void run() {exec();}

private:
   QSqlDatabase db;
};

Затем вы переопределите QEvent, чтобы включить все, что вам нужно для выполнения вашего запроса. Это создает только один поток и только одно соединение. Вам не нужно создавать какие-либо очереди и вам не нужно обрабатывать параллелизм. Если вам нужно знать, когда ваш запрос завершен, вы можете создавать сигналы в конце запроса. Чтобы запросить новый запрос, вы можете просто сделать что-то вроде этого:

QCoreApplication::postEvent(<pointer_to_thread_instance>, <pointer_to_event_instance>);

Еще один хороший подход - использовать пул QThreads, каждый из которых имеет собственное соединение. Это в любом случае может быть полезно, если вам нужен параллелизм.

0 голосов
/ 25 октября 2015

Эта статья мне очень помогла Асинхронный доступ к базе данных с Qt 4.x .Я думаю, что создание рабочего объекта в потоке и использование соединения с очередями для вызова его слотов лучше, чем запуск новых событий и публикация их в потоке.Вы можете скачать образцы файлов по этой ссылке.

0 голосов
/ 16 октября 2011

IIRC правильно, эта проблема имеет гораздо больше отношения к бэкэнду, чем Qt.Например, в прошлом - это все еще возможно - PostgreSQL требовал, чтобы каждый поток имел свое собственное соединение, но у MySQL были другие способы работы с потоками.Пока вы играли по правилам бэкэнда, все работало нормально.

Раньше для PostgreSQL я создавал систему, в которой я выталкивал QSqlQuery в очередь, а другой поток очищал очередь., выполнил запрос и передал sqlresult обратно.Это было хорошо, пока я всегда использовал одно и то же «резьбовое» соединение.Неважно, что я создал соединение в главном потоке, это имело значение только тогда, когда пришло время для выполнения.

QtConcurrent был бы хорошим соответствием для этой системы, хотя на самом деле только на одном уровневремя.Это освободило бы главный поток.

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

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

...