一、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"