Правильно использовать QSqlDatabase в многопоточных программах

Основываясь на документации Qt:

Соединение может использоваться только из потока, который его создал. Перемещение соединений между потоками или создание запросов из другого потока не поддерживается.

Вопрос, который меня беспокоит, - это то, что происходит при экземпляре базы данных copy-construct. Например, вот код в основном потоке:

int main(int argc, char** argv) {
...
    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "DB1");
    db.setHostName("localhost");
...

и вот соединение в рабочих потоках:

void MyThread::run() {
    QSqlDatabase db(QSqlDatabase::database("DB1"));
    if (db.open()) {
    ...
}

Является ли этот поток безопасным или нет? Как правило, такая операция была бы безопасной в С++, но поскольку QT использует неявный обмен и привязку потоков, я уже не уверен.

Говорят: соединение можно использовать только внутри потока, который его создал, но что это значит? Является QSqlDatabase:: addDatabase точкой, где создается соединение, или фактически, когда вызывается функция open().

UPDATE:

После ответа от Laszlo Papp и, в конечном счете, изучения исходного кода Qt, я должен сказать, что дизайн этой части Qt выглядит ошибочным для меня.

Если я правильно понимаю, QSqlDatabase использует неявный совлокальный доступ под капотом, но, к сожалению, это не истинный неявный совлокальный доступ, поскольку экземпляр-экземпляр экземпляра QSqlDatabase не создаст новый экземпляр общих данных, когда это необходимо. Чтобы ухудшить ситуацию, вы не можете создавать временное соединение, но вместо этого вы должны использовать статические методы addDatabase/removeDatabase, и в этом случае вам нужно синхронизировать потоки, чтобы избежать столкновения имен.

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

Все это заставляет меня задуматься о целях проектирования, и если неявное совместное использование подходит для этого конкретного случая. ИМХО, гораздо лучшим решением было бы позволить конструктору копирования действительно выполнять эту работу и сделать копию подключения для вас. Те, кто не хочет иметь личные/временные копии, все еще могут использовать addDatebase/removeDatabase, и в этом случае метод database() необходимо изменить для возврата ссылки.

Ответы

Ответ 1

Говорят: соединение можно использовать только внутри потока, который его создал, но что это значит? Является QSqlDatabase:: addDatabase точкой, где создается соединение, или на самом деле при вызове функции open().

Первый. Подробнее см. :

Класс QSqlDatabase представляет соединение с базой данных.

Класс QSqlDatabase предоставляет интерфейс для доступа к базе данных через соединение. Экземпляр QSqlDatabase представляет соединение. Соединение обеспечивает доступ к базе данных через один из поддерживаемых драйверов баз данных, которые получены из QSqlDriver. Кроме того, вы можете подклассифицировать свой собственный драйвер базы данных из QSqlDriver. См. Раздел "Как написать свой собственный драйвер базы данных". Дополнительная информация.

Создайте соединение (например, экземпляр QSqlDatabase), вызвав одну из статических функций addDatabase()...

Это последнее предложение должно очистить вашу заботу.

Ответ 2

Вы можете использовать QSqlDatabase::cloneDatabase для получения "реальной" копии базы данных, которая готова к открытию в любом потоке.

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