Использование соединения QSqlDatabase в QtConcurrent :: run (пул псевдо-соединений)

3

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

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

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

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


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

Теги:
qt
asynchronous
database-connection

4 ответа

0
Лучший ответ

В соответствии с документацией 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, чтобы включить все, что вам нужно для выполнения вашего запроса. Это создает только один поток и только одно соединение. Вам не нужно создавать какую-либо очередь, и вам не нужно обрабатывать concurrency. Если вам нужно знать, когда ваш запрос закончен, вы можете создавать сигналы в конце запроса. Чтобы запросить новый запрос, вы можете просто сделать что-то вроде этого:

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

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

  • 0
    Я пытаюсь избежать другого потока, поскольку возможность прямого вызова многопоточных функций (цель QtConcurrent) действительно удобна по сравнению с механизмом связи сигналов / слотов.
  • 0
    Сигнал / слот не используется. Я не понимаю, почему вы говорите, что это удобно по сравнению, например, с QMetaObject. В любом случае, извините, я не знаю ни одного способа использовать одно соединение только с QtConcurrent :: run (). Лучшее, что я могу придумать, это одно соединение для каждого используемого им потока. Надеюсь, кто-то может помочь.
Показать ещё 1 комментарий
0

Мое решение состоит в использовании 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

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

0

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

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

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

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

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

  • 0
    Так как наша БД просто сконфигурирована в файл, я не могу полагаться на какой-либо серверной части.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню