天天看點

關于Qt多線程操作資料庫

前幾天用Qt寫了一個基于線程池的Tcp伺服器,發現掉線很頻繁,追蹤日志發現大多是因為資料庫連接配接名稱的沖突導緻的,這裡記錄一下。

運作環境: ubuntu16 Qt5.9.6

目前線程建立資料庫對象和查詢對象隻能在目前線程中使用,不能跨線程使用

這裡說的是一個線程建立的 QSqlDatabase 對象和 查出來的 QSqlQuery 對象隻能在目前線程中使用。一個資料庫連接配接本身比如一個連接配接的名稱是可以在不同線程中使用的。預設連接配接名稱是 “qt_sql_default_connection”

char *QSqlDatabase::defaultConnection = const_cast<char *>("qt_sql_default_connection");

12

多線程連接配接資料庫

1、共用一個連接配接

線程1,建立連接配接,然後不斷地去查詢:

void Thread1::run()

{

    for (;;)

    {

        QString connectionName = "connection";

        {

            QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);

            db.setHostName("127.0.0.1");

            db.setDatabaseName("testdb");

            db.setUserName("root");

            db.setPassword("1234");

            if (!db.open())

            {

                qDebug() << "thread 1 open db failed!";

            }

            while (1)

            {

                QSqlQuery query("SELECT * FROM testtb", db);

                qDebug() << "thread1 error: " << query.lastError();

                while (query.next())

                {

                    qDebug() << "thread1 value:" << query.value(0).toInt();

                }

                QThread::msleep(100);

            }

        }

    }

}

1234567891011121314151617181920212223242526272829

線程2,擷取線程一建立的連接配接名稱,然後不斷地查詢:

void Thread2::run()

{

    for (;;)

    {

        QString connectionName = "connection";

        {

            QSqlDatabase db = QSqlDatabase::database(connectionName);

            while (1)

            {

                QSqlQuery query("SELECT * FROM testtb", db);

                qDebug() << "thread2 error: " << query.lastError();

                while (query.next())

                {

                    qDebug() << "thread2 value:" << query.value(0).toInt();

                }

                QThread::msleep(100);

            }

        }

        msleep(100);

    }

}

123456789101112131415161718192021

main函數:

int main(int argc, char *argv[])

{

    QCoreApplication a(argc, argv);

    Thread1 *th1 = new Thread1;

    th1->start();

    QThread::msleep(100);

    Thread2 *th2 = new Thread2;

    th2->start();

    return a.exec();

}

123456789101112

QSqlDatabase::database函數是線程安全的。但是還有一個問題,如果運作上面的代碼會出警告:

thread2 error:  QSqlError("2013", "QMYSQL: Unable to execute query", "Lost connection to MySQL server during query")

thread2 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread1 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread2 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread1 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread1 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread2 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread1 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread2 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread2 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread1 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread1 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread2 error:  QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")

thread1 error:  QSqlError("0", "QMYSQL: Unable to execute query", "")

1234567891011121314

多線程通路一個連接配接,會存在資源競争,加鎖保護:

...

while (1)

{

    gMutex.lock();

    QSqlQuery query("SELECT * FROM testtb", db);

    qDebug() << "thread1 error: " << query.lastError();

    while (query.next())

    {

        qDebug() << "thread1 value:" << query.value(0).toInt();

    }

    gMutex.unlock();

    QThread::msleep(100);

}

...

while (1)

{

    gMutex.lock();

    QSqlQuery query("SELECT * FROM testtb", db);

    qDebug() << "thread2 error: " << query.lastError();

    while (query.next())

    {

        qDebug() << "thread2 value:" << query.value(0).toInt();

    }

    gMutex.unlock();

    QThread::msleep(100);

}

...

123456789101112131415161718192021222324252627

問題解決。

2、多線程動态連接配接資料庫

動态連接配接資料庫我們采用線程池的方式。為了保證每個線程的連接配接名稱不同,将線程id相關資訊設定成連接配接名稱。

tashk.h:

#ifndef TASK_H

#define TASK_H

#include <QObject>

#include <QRunnable>

class Task : public QObject, public QRunnable

{

    Q_OBJECT

public:

    explicit Task(QObject *parent = nullptr);

    void setType(const int &type);

protected:

    void run();

private:

    void thread1Workder();

    void thread2Workder();

    void thread3Workder();

    void thread4Workder();

private:

    int m_type;

};

#endif // TASK_H

1234567891011121314151617181920212223242526272829

task.cpp:

#include "task.h"

#include <QDebug>

#include <QThread>

#include <QSqlDatabase>

#include <QDateTime>

Task::Task(QObject *parent)

    : QObject(parent),

      m_type(0)

{

}

void Task::setType(const int &type)

{

    m_type = type;

}

void Task::run()

{

    switch (m_type) {

    case 0:

        thread1Workder();

        break;

    case 1:

        thread2Workder();

        break;

    case 2:

        thread3Workder();

        break;

    case 3:

        thread4Workder();

        break;

    default:

        break;

    }

}

void Task::thread1Workder()

{

    QString connectionName = QString::number(*static_cast<int*>(QThread::currentThreadId())); 

    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);

    db.setHostName("127.0.0.1");

    db.setDatabaseName("testdb");

    db.setUserName("root");

    db.setPassword("1234");

    if (!db.open())

    {

        qDebug() << "thread 1 open db failed!";

    }

    qDebug() << "thread 1: " << QThread::currentThreadId() << connectionName;  

}

void Task::thread2Workder()

{

    QString connectionName = QString::number(*static_cast<int*>(QThread::currentThreadId()));

    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);

    db.setHostName("127.0.0.1");

    db.setDatabaseName("testdb");

    db.setUserName("root");

    db.setPassword("1234");

    if (!db.open())

    {

        qDebug() << "thread 1 open db failed!";

    }

    qDebug() << "thread 2: " << QThread::currentThreadId() << connectionName;

}

void Task::thread3Workder()

{

    QString connectionName = QString::number(*static_cast<int*>(QThread::currentThreadId()));

    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);

    db.setHostName("127.0.0.1");

    db.setDatabaseName("testdb");

    db.setUserName("root");

    db.setPassword("1234");

    if (!db.open())

    {

        qDebug() << "thread 1 open db failed!";

    }

    qDebug() << "thread 3: " << QThread::currentThreadId() << connectionName;

}

void Task::thread4Workder()

{

    QString connectionName = QString::number(*static_cast<int*>(QThread::currentThreadId()));

    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);

    db.setHostName("127.0.0.1");

    db.setDatabaseName("testdb");

    db.setUserName("root");

    db.setPassword("1234");

    if (!db.open())

    {

        qDebug() << "thread 1 open db failed!";

    }

    qDebug() << "thread 4: " << QThread::currentThreadId() << connectionName;

}

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697

main.cpp:

#include <QCoreApplication>

#include <QThreadPool>

#include <QDebug>

#include "task.h"

int main(int argc, char *argv[])

{

    QCoreApplication a(argc, argv);

    int index = 0;

    for (;;)

    {

        Task *pTask = new Task;

        pTask->setAutoDelete(true);

        pTask->setType(index);

        QThreadPool::globalInstance()->start(pTask);

        index ++;

        if (index > 3)

            index =0;

    }

    return a.exec();

}

12345678910111213141516171819202122

運作結果:

QSqlDatabasePrivate::addDatabase: duplicate connection name '469759744', old connection removed.

QSqlDatabasePrivate::addDatabase: duplicate connection name '701179648', old connection removed.

12

發現大量重複連接配接名稱的警告。那我們在每一個線程連接配接完成之後調用QSqlDatabase::removeDatabase()方法,該方法是線程安全的。如下:

void Task::thread1Workder()

{

    QString connectionName = QString::number(*static_cast<int*>(QThread::currentThreadId())); 

    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);

    db.setHostName("127.0.0.1");

    db.setDatabaseName("testdb");

    db.setUserName("root");

    db.setPassword("1234");

    if (!db.open())

    {

        qDebug() << "thread 1 open db failed!";

    }

    qDebug() << "thread 1: " << QThread::currentThreadId() << connectionName; 

    QSqlDatabase::removeDatabase(connectionName); 

}

void Task::thread2Workder() { ... }

void Task::thread3Workder() { ... }

void Task::thread4Workder() { ... }

123456789101112131415161718

運作時發現還是有警告,大概意思是連接配接正在使用,将停止工作。

hread 2:  0x7ff658a5f700 "1487271680"

QSqlDatabasePrivate::removeDatabase: connection '1487271680' is still in use, all queries will cease to work.

thread 1 open db failed!

thread 3:  0x7ff659a61700 "1504057088"

1234

調用close()方法也沒什麼用,查了一下官方文檔:

大概意思是不應該在有在該連接配接上進行資料查詢時調用該函數,否則會記憶體洩露。雖然這裡并沒有查詢任務,但我們按它的方式改下試試:

void Task::thread1Workder()

{

    QString connectionName = QString::number(*static_cast<int*>(QThread::currentThreadId()));

    {

        QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);

        db.setHostName("127.0.0.1");

        db.setDatabaseName("testdb");

        db.setUserName("root");

        db.setPassword("1234");

        if (!db.open())

        {

            qDebug() << "thread 1 open db failed!";

        }

        qDebug() << "thread 1: " << QThread::currentThreadId() << connectionName;

    }

    QSqlDatabase::removeDatabase(connectionName);

}

void Task::thread2Workder() { ... }

void Task::thread3Workder() { ... }

void Task::thread4Workder() { ... }

1234567891011121314151617181920

運作發現可以了。不再出現警告了。但是如果運作一段時間會發現程式偶爾會挂掉。。。

原因(https://blog.csdn.net/goldenhawking/article/details/10811409):

Qt 會動态的加載資料庫的plugin, 加載 plugin 的部分,涉及到對本地庫檔案的管理,這一部分,出現了競争。于是,很自然的想到在初始連接配接部分設定 Mutex 保護,從 addDatabase / database到 open 的部分,要保證其原子性,問題再也沒有出現。

再稍微改下,經過大量測試發現正常了:

void Task::thread1Workder()

{

    QString connectionName = QString::number(*static_cast<int*>(QThread::currentThreadId()));

    {

        mutex.lock();

        QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName);

        db.setHostName("127.0.0.1");

        db.setDatabaseName("testdb");

        db.setUserName("root");

        db.setPassword("1234");

        if (!db.open())

        {

            qDebug() << "thread 1 open db failed!";

        }

        mutex.unlock();

        qDebug() << "thread 1: " << QThread::currentThreadId() << connectionName;

    }

    QSqlDatabase::removeDatabase(connectionName);

}

void Task::thread2Workder() { ... }

void Task::thread3Workder() { ... }

void Task::thread4Workder() { ... }

12345678910111213141516171819202122

總結

1、一個線程建立的資料庫對象隻能在目前線程使用,但是建立的連接配接可以多線程使用,唯一需要注意的是,在調用全局方法的時候,要有原子保護。

2、removedatabase方法是線程安全的,但是在調用的時候確定QSqlDatabase和QSqlQuery等對象已經釋放,否則會造成記憶體洩漏。

3、多線程通路時不管是同一連接配接名還是不同連接配接名稱都要進行資源保護。

--------------------- 

作者:FlyWM_ 

來源:CSDN 

原文:https://blog.csdn.net/a844651990/article/details/82696136 

版權聲明:本文為部落客原創文章,轉載請附上博文連結!