在多线程程序中正确使用QSqlDatabase

问题描述 投票:0回答:3

基于 Qt 文档:

连接只能在创建它的线程内使用。不支持在线程之间移动连接或从不同线程创建查询。

困扰我的问题是当我复制构造数据库实例时会发生什么。例如,这是主线程中的代码:

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()) {
    ...
}

这个线程安全吗?通常,这样的操作在 C++ 中是安全的,但由于 QT 使用隐式共享和线程关联,我不再确定。

他们说:连接只能在创建它的线程中使用,但这是什么意思呢? QSqlDatabase::addDatabase 是创建 connection 的点还是实际调用 open() 函数时的点。

更新:

在 Laszlo Papp 的回答并最终查看 Qt 源代码之后,我必须说 Qt 这部分的设计对我来说看起来有缺陷。

如果我理解正确的话,QSqlDatabase 在底层使用隐式共享,但不幸的是它不是真正的隐式共享,因为 QSqlDatabase 实例的复制构造函数在需要时不会创建共享数据的新实例。更糟糕的是,您无法创建临时连接,而是必须使用静态方法 addDatabase/removeDatabase,在这种情况下您必须同步线程以避免名称冲突。

这当然使得在 QtConcurrent 中使用 QSqlDatabase 非常困难,特别是如果连接应该深埋在某些抽象后面的话。由于我们不知道代码将在哪个线程上运行,因此我们无法在两个调用之间保持连接打开。如果我们想生成动态数量的任务,我们需要确保任务不使用相同的数据库名称。

所有这些让我想知道设计目标以及隐式共享是否适合这种特殊情况。恕我直言,更好的解决方案是让复制构造函数真正完成它的工作并为您复制连接。那些不想拥有私有/临时副本的人仍然可以使用 addDatebase/removeDatabase,在这种情况下需要修改方法database()以返回引用。

c++ multithreading qt qtsql
3个回答
6
投票

他们说:连接只能在创建它的线程内使用,但这是什么意思? QSqlDatabase::addDatabase 是创建连接的点还是实际调用 open() 函数时的点。

前者。详细信息请参阅文档

QSqlDatabase 类表示与数据库的连接。

QSqlDatabase 类提供了通过连接访问数据库的接口。 QSqlDatabase 的实例代表连接。该连接通过受支持的数据库驱动程序之一提供对数据库的访问,这些驱动程序派生自 QSqlDriver。或者,您可以从 QSqlDriver 子类化您自己的数据库驱动程序。有关更多信息,请参阅如何编写自己的数据库驱动程序。

通过调用静态 addDatabase() 函数之一创建连接(即 QSqlDatabase 的实例)...

最后一句话应该可以消除您的疑虑。


6
投票

您可以使用

QSqlDatabase::cloneDatabase
获取数据库的“真实”副本,该副本可以在任何线程中打开。

您需要在初始化克隆数据库的线程中执行此操作,但您可以将获得的尚未打开的数据库移动到任何线程并在那里使用它。


0
投票

从 Qt 6.8 开始,有一个新的 QSqlDatabase 函数,支持将 QSqlDatabase 实例移动到不同的线程:

bool QSqlDatabase::moveToThread(QThread *targetThread)

Qt 文档

© www.soinside.com 2019 - 2024. All rights reserved.