Qt开发:QThreadStorage 介绍和使用

一、QThreadStorage简介

QThreadStorage 是 Qt 中提供的 线程局部存储(TLS, Thread-Local Storage) 工具,它可以为每个线程维护一个独立的值副本。适合用来在线程中保存一些“只属于当前线程”的数据,例如缓存、日志上下文、线程内对象等。换句话说QThreadStorage<\T> 就像QMap<QThread*, T*>,但由 Qt 自动管理生命周期,每个线程拥有自己的 T 实例,线程结束后自动释放。

类定义:

template <typename T>
class QThreadStorage
  • 它是一个模板类,通常你用 QThreadStorage<MyType> 声明。
  • 每个线程调用 .localData() 得到的是属于当前线程的 T 对象。

二、QThreadStorage的常用方法

在这里插入图片描述

三、QThreadStorage的使用示例

示例一:每个线程维护一个 QString 缓存

#include <QThread>
#include <QThreadStorage>
#include <QDebug>

QThreadStorage<QString *> threadString;

void useThreadStorage(const QString &value) {
    if (!threadString.hasLocalData()) {
        threadString.setLocalData(new QString());
    }

    *threadString.localData() = value;
    qDebug() << "Thread:" << QThread::currentThread() << "Value:" << *threadString.localData();
}

在线程中使用:

class MyWorker : public QThread {
    void run() override {
        useThreadStorage("ThreadLocal_" + QString::number((quintptr)QThread::currentThreadId()));
        sleep(1);
    }
};

自动释放:

  • QThreadStorage 在线程退出时自动释放 T* 指针数据(如果是指针类型)。
  • 如果你使用非指针类型如 QThreadStorage<QString>,会自动构造/析构。

示例二:存储非指针类型

QThreadStorage<QString> threadString;

void useStorage() {
    QString &s = threadString.localData(); // 如果没有,会默认构造一个空字符串
    s = QString("Data in thread %1").arg((quintptr)QThread::currentThreadId());
    qDebug() << s;
}

注意事项:

  • 推荐存储指针类型(如 QThreadStorage<MyObject*>),否则 Qt 每次创建新副本不太可控。
  • 指针型数据应通过 new 分配,Qt 会自动在 QThread 结束时 delete 掉。
  • 不适合在线程间共享数据,只是每个线程一份私有副本。

四、QThreadStorage的项目实战

有一个使用 QThreadPool 管理任务的 Qt 项目,希望:

  • 每个线程仅初始化一个数据库连接(例如 SQLite、MySQL)。
  • 每个任务执行时可以从线程本地获取自己的连接。

目标功能:
在这里插入图片描述

模块结构:

  • ThreadLocalDatabase:封装线程私有连接,带连接检测 + 重连机制
  • DbTask:数据库任务,使用连接执行 SQL
  • 主函数:向线程池提交任务

4.1 ThreadLocalDatabase 封装

// ThreadLocalDatabase.h

#pragma once

#include <QThreadStorage>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QDebug>

class ThreadLocalDatabase {
public:
    static QSqlDatabase& connection()
    {
        if (!dbStorage.hasLocalData()) {
            initConnection();
        }

        // 检查连接是否有效
        QSqlDatabase& db = *dbStorage.localData();
        if (!db.isOpen()) {
            qWarning() << "数据库连接断开,尝试重连...";
            db = reconnect();
        }

        return db;
    }

private:
    static QThreadStorage<QSqlDatabase*> dbStorage;

    static void initConnection()
    {
        QString connName = QString("db_%1").arg((quintptr)QThread::currentThreadId());

        QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connName);
        db.setDatabaseName("mydata.db");
		
		/*QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connName);
        db.setHostName("localhost");
        db.setPort(3306);
        db.setDatabaseName("testdb");
        db.setUserName("root");
        db.setPassword("123456");*/

        if (!db.open()) {
            qCritical() << "初次连接失败:" << db.lastError().text();
        }

        dbStorage.setLocalData(new QSqlDatabase(db));
    }

    static QSqlDatabase reconnect()
    {
        QSqlDatabase& db = *dbStorage.localData();
        QString connName = db.connectionName();

        // 先移除旧连接
        db.close();
        QSqlDatabase::removeDatabase(connName);

        // 创建新连接
        QSqlDatabase newDb = QSqlDatabase::addDatabase("QSQLITE", connName);
        newDb.setDatabaseName("mydata.db");

		/*QSqlDatabase newDb = QSqlDatabase::addDatabase("QMYSQL", connName);
        newDb.setHostName("localhost");
        newDb.setPort(3306);
        newDb.setDatabaseName("testdb");
        newDb.setUserName("root");
        newDb.setPassword("123456");*/

        if (!newDb.open()) {
            qCritical() << "重连失败:" << newDb.lastError().text();
        } else {
            qDebug() << "重连成功:" << connName;
        }

        *dbStorage.localData() = newDb;
        return *dbStorage.localData();
    }
};
// ThreadLocalDatabase.cpp
#include "ThreadLocalDatabase.h"
QThreadStorage<QSqlDatabase*> ThreadLocalDatabase::dbStorage;

4.2 DbTask 数据库任务类

// DbTask.h

#pragma once

#include <QRunnable>
#include <QSqlQuery>
#include <QSqlError>
#include "ThreadLocalDatabase.h"

class DbTask : public QRunnable
{
public:
    DbTask(QString message) : msg(std::move(message)) {}

    void run() override
    {
        QSqlDatabase db = ThreadLocalDatabase::connection();

        QSqlQuery query(db);
        query.exec("CREATE TABLE IF NOT EXISTS logs(id INTEGER PRIMARY KEY AUTOINCREMENT, msg TEXT)");

        query.prepare("INSERT INTO logs(msg) VALUES (:msg)");
        query.bindValue(":msg", msg);

        if (!query.exec()) {
            qWarning() << "插入失败:" << query.lastError().text();
        } else {
            qDebug() << "插入成功:" << msg;
        }
    }

private:
    QString msg;
};

4.3 使用线程池执行任务

// main.cpp

#include <QCoreApplication>
#include <QThreadPool>
#include "DbTask.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    QThreadPool* pool = QThreadPool::globalInstance();
    pool->setMaxThreadCount(4);  // 设置线程池大小

    for (int i = 0; i < 20; ++i) {
        QString message = QString("Log from thread %1").arg(i);
        pool->start(new DbTask(message));
    }

    pool->waitForDone(); // 等待所有任务完成
    return 0;
}

自动事务改造 DbTask:
在 run() 函数中加上如下三步:

  • db.transaction() 开启事务
  • db.commit() 提交事务
  • 出错时 db.rollback() 回滚事务

完整示例:自动事务的 DbTask.h

#pragma once

#include <QRunnable>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlDatabase>
#include <QThread>
#include <QElapsedTimer>
#include <QDebug>
#include "ThreadLocalDatabase.h"

class DbTask : public QRunnable
{
public:
    explicit DbTask(QString msg) : message(std::move(msg)) {}

    void run() override
    {
        const int maxRetries = 3;
        const int retryDelayMs = 200;

        QSqlDatabase db = ThreadLocalDatabase::connection();

        if (!db.isOpen()) {
            qCritical() << "数据库未连接";
            return;
        }

        if (!db.transaction()) {
            qWarning() << "开启事务失败:" << db.lastError().text();
            return;
        }

        QSqlQuery query(db);
        if (!query.exec("CREATE TABLE IF NOT EXISTS logs ("
                        "id INT AUTO_INCREMENT PRIMARY KEY, "
                        "msg TEXT)")) {
            qWarning() << "建表失败:" << query.lastError().text();
            db.rollback();
            return;
        }

        query.prepare("INSERT INTO logs(msg) VALUES (:msg)");
        query.bindValue(":msg", message);
        if (!query.exec()) {
            qWarning() << "插入失败:" << query.lastError().text();
            db.rollback();
            return;
        }

        // ---------- commit() 重试 + 耗时 ----------
        bool commitSuccess = false;
        QElapsedTimer timer;
        for (int attempt = 1; attempt <= maxRetries; ++attempt) {
            timer.start();
            bool ok = db.commit();
            qint64 ms = timer.elapsed();

            if (ok) {
                qDebug() << QString("✅ 提交成功(第 %1 次尝试,用时 %2ms): %3")
                                .arg(attempt).arg(ms).arg(message);
                commitSuccess = true;
                break;
            } else {
                qWarning() << QString("❌ 提交失败(第 %1 次,用时 %2ms): %3")
                                  .arg(attempt).arg(ms).arg(db.lastError().text());
                if (attempt < maxRetries) {
                    QThread::msleep(retryDelayMs);
                }
            }
        }

        if (!commitSuccess) {
            qCritical() << "所有 commit 尝试失败,执行回滚";
            db.rollback();
        }
    }

private:
    QString message;
};

输出示例:

提交成功(第 1 次尝试,用时 8ms): "MySQL log from task 3"
提交失败(第 1 次,用时 12ms): "Lost connection"
提交失败(第 2 次,用时 15ms): "Connection timed out"
提交成功(第 3 次尝试,用时 10ms): "MySQL log from task 6"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值