详解 QThreadStorage 的实现机制
在多线程环境中,当每个线程需要独立维护某些数据时,就需要用到线程本地存储(TLS, Thread-Local Storage)。不同的操作系统 TLS 的实现方式也不相同,但都提供了相应的接口。QT 作为一个可跨平台的编程工具,封装了不同操作系统对 TLS 的操作,通过 QThreadStorage 类为每个线程提供独立数据存储。
1. QThreadStorage 官方说明
QThreadStorage 是一个为线程提供独立数据存储的模板类,通过hasLocalData()、localData()、setLocalData() 访问和存储线程数据。如果 QThreadStorage 存储指针类型的数据,数据必须通过关键字 new 在堆上创建,QThreadStorage 拥有该数据的管理权,并在线程退出(正常结束或终止)时删除该数据。
注意:
- QThreadStorage 的析构函数不会删除每个线程的数据,只有线程退出或多次调用 setLocalData() 时才会删除线程数据。
- QThreadStorage 可用于存储主线程的数据。当 QApplication 销毁时(不管主线程是否结束),QThreadStorage 会删除其存储的主线程数据。
2. window 中 TLS 的操作方法
windows 平台中 TLS 索引通常由 TlsAlloc 函数在进程或 DLL 初始化期间分配。分配 TLS 索引后每个线程都有自己的索引槽。在分配 TLS 索引时,线程存储槽初始化为 NULL。进程的每个线程使用 TLS 索引来访问自己的 TLS 存储槽。线程存储数据时,指定 TLS 索引,调用 TlsSetValue 方法在其槽中存储值。线程检索存储数据时,指定相同的索引,调用用 TlsGetValue 检索存储的值。
下图说明了 TLS 的工作原理。

进程有两个线程:线程 1 和线程 2,分配两个 TLS 索引: gdwTlsIndex1 和 gdwTlsIndex2。 每个线程分配两个内存块(一个用于每个索引),用于存储数据,并将指向这些内存块的指针存储在相应的 TLS 槽中。 若要访问与索引关联的数据,线程将从 TLS 槽检索指向内存块的指针,并将其存储在 lpvData 本地变量中。
使用线程本地存储的示例如下:
#include <windows.h>
#include <stdio.h>
#define THREADCOUNT 4
// TLS 索引
DWORD dwTlsIndex;
VOID ErrorExit (LPCSTR message);
VOID CommonFunc(VOID)
{
LPVOID lpvData;
// 获取线程本地存储的数据
lpvData = TlsGetValue(dwTlsIndex);
if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS))
ErrorExit("TlsGetValue error");
// 打印数据
printf("common: thread %d: lpvData=%lx\n",
GetCurrentThreadId(), lpvData);
Sleep(5000);
}
DWORD WINAPI ThreadFunc(VOID)
{
LPVOID lpvData;
// 初始化线程本地数据
lpvData = (LPVOID) LocalAlloc(LPTR, 256);
// 存储线程本地数据
if (! TlsSetValue(dwTlsIndex, lpvData))
ErrorExit("TlsSetValue error");
printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData);
CommonFunc();
// 释放本地数据的存储空间
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != 0)
LocalFree((HLOCAL) lpvData);
return 0;
}
int main(VOID)
{
DWORD IDThread;
HANDLE hThread[THREADCOUNT];
int i;
// 分配 TLS 索引
if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
ErrorExit("TlsAlloc failed");
// 创建线程
for (i = 0; i < THREADCOUNT; i++)
{
hThread[i] = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE) ThreadFunc,
NULL, 0, &IDThread);
if (hThread[i] == NULL)
ErrorExit("CreateThread error\n");
}
for (i = 0; i < THREADCOUNT; i++)
WaitForSingleObject(hThread[i], INFINITE);
// 释放 TLS 索引
TlsFree(dwTlsIndex);
return 0;
}
VOID ErrorExit (LPCSTR message)
{
fprintf(stderr, "%s\n", message)
ExitProcess(0);
}
3. linux 中 TLS 的操作方法
Linux 中线程本地存储成为 TSD(Thread specific data),它是 POSIX 标准的一部分。每个线程都拥有一个私有内存块,即线程专用的数据区,简称 TSD 区。该区域由 TSD 键索引。TSD 键在所有线程中都是通用的,每个 TSD 键在每个线程中都指向自己的 TSD 区域。TSD 区域存储 void * 类型值。TSD 区域可以看作存储 void * 类型的数组,TSD 键作为整数索引,TSD 键对应数组元素的值的就是线程里存储的值。线程创建时,TSD 键对应的区域都初始化为 NULL。
函数 pthread_key_create()用于新的 TSD 键。在给定时间分配的密钥数量存在一个上限 PTHREAD_KEYS_MAX。 在所有已运行的线程中,新建的 TSD 键所关联的值都为 NULL。
函数 pthread_key_delete() 用于收回给 TSD 键分配的空间。
函数 pthread_setspecific() 用于设置线程中 TSD 键对应的值。
函数 pthread_getspecific() 用于获取线程中 TSD 键对应的值。
使用示例如下:
/* 声明 TSD 键 */
static pthread_key_t buffer_key;
/* 仅初始化一次 */
static pthread_once_t buffer_key_once = THREAD_ONCE_INIT;
/* 创建线程专用缓冲区 */
void buffer_alloc(void)
{
pthread_once(&buffer_key_once, buffer_key_alloc);
pthread_setspecific(buffer_key, malloc(100));
}
/* 返回线程专用缓冲区 */
char * get_buffer(void)
{
return (char *) pthread_getspecific(buffer_key);
}
/* 分配 TSD 键 */
static void buffer_key_alloc()
{
pthread_key_create(&buffer_key, buffer_destroy);
}
/* 释放线程专用缓冲区 */
static void buffer_destroy(void * buf)
{
free(buf);
}
4. QThreadStorage 源码分析
源码版本为 QT6.8.3,只列出关键源码。
下面是 QThreadStorage 的声明,可以看出数据的存取操作由 QThreadStorageData 来完成。QThreadStorageData 的 get() 方法用于获取线程私有数据,set()方法用于设置线程私有数据, id 是QThreadStorage 对象的唯一标识。
//\qtbase\src\corelib\thread\qthreadstorage.h
...
class Q_CORE_EXPORT QThreadStorageData
{
public:
explicit QThreadStorageData(void (*func)(void *));
~QThreadStorageData();
void** get() const;
void** set(void* p);
static void finish(void**);
int id;
};
#if !defined(QT_MOC_CPP)
// MOC_SKIP_BEGIN
// pointer specialization
template <typename T>
inline
T *&qThreadStorage_localData(QThreadStorageData &d, T **)
{
void **v = d.get();
if (!v) v = d.set(nullptr);
return *(reinterpret_cast<T**>(v));
}
template <typename T>
inline
T *qThreadStorage_localData_const(const QThreadStorageData &d, T **)
{
void **v = d.get();
return v ? *(reinterpret_cast<T**>(v)) : 0;
}
template <typename T>
inline
void qThreadStorage_setLocalData(QThreadStorageData &d, T **t)
{ (void) d.set(*t); }
template <typename T>
inline
void qThreadStorage_deleteData(void *d, T **)
{ delete static_cast<T *>(d); }
// value-based specialization
template <typename T>
inline
T &qThreadStorage_localData(QThreadStorageData &d, T *)
{
void **v = d.get();
if (!v) v = d.set(new T());
return *(reinterpret_cast<T*>(*v));
}
template <typename T>
inline
T qThreadStorage_localData_const(const QThreadStorageData &d, T *)
{
void **v = d.get();
return v ? *(reinterpret_cast<T*>(*v)) : T();
}
template <typename T>
inline
void qThreadStorage_setLocalData(QThreadStorageData &d, T *t)
{ (void) d.set(new T(*t)); }
template <typename T>
inline
void qThreadStorage_deleteData(void *d, T *)
{ delete static_cast<T *>(d); }
// MOC_SKIP_END
#endif
template <class T>
class QThreadStorage
{
private:
QThreadStorageData d;
Q_DISABLE_COPY(QThreadStorage)
static inline void deleteData(void *x)
{ qThreadStorage_deleteData(x, reinterpret_cast<T*>(0)); }
public:
inline QThreadStorage() : d(deleteData) { }
inline ~QThreadStorage() { }
inline bool hasLocalData() const
{ return d.get() != nullptr; }
inline T& localData()
{ return qThreadStorage_localData(d, reinterpret_cast<T*>(0)); }
inline T localData() const
{ return qThreadStorage_localData_const(d, reinterpret_cast<T*>(0)); }
inline void setLocalData(T t)
{ qThreadStorage_setLocalData(d, &t); }
};
QThreadStorageData 的定义如下。从源码中可以看出具体的 TLS 数据来自 QThreadData,QThreadStorageData 的 get 或 set 方法都是利用 id 在 QThreadData 的 tls 中查找对应的值。
//\qtbase\src\corelib\thread\qthreadstorage.cpp
...
// 此处定义了一个全局的 QList<void (*)(void *)> destructors
// 用于存储 TLS 数据的清理方法,同时也是 QThreadStorage 对象标识生成的依据
typedef QList<void (*)(void *)> DestructorMap;
Q_GLOBAL_STATIC(DestructorMap, destructors)
QThreadStorageData::QThreadStorageData(void (*func)(void *))
{
QMutexLocker locker(&destructorsMutex);
DestructorMap *destr = destructors();
if (!destr) {
...
QThreadData *data = QThreadData::current();
id = data->tls.size();
...
return;
}
for (id = 0; id < destr->size(); id++) {
if (destr->at(id) == nullptr)
break;
}
// destructors 遍历完成的同时生成了 QThreadStorage 的唯一标识
if (id == destr->size()) {
destr->append(func);
} else {
(*destr)[id] = func;
}
...
}
QThreadStorageData::~QThreadStorageData()
{
DEBUG_MSG("QThreadStorageData: Released id %d", id);
QMutexLocker locker(&destructorsMutex);
if (destructors())
(*destructors())[id] = nullptr;
}
void **QThreadStorageData::get() const
{
QThreadData *data = QThreadData::current();
if (!data) {
qWarning("QThreadStorage::get: QThreadStorage can only be used with threads started with QThread");
return nullptr;
}
QList<void *> &tls = data->tls;
if (tls.size() <= id)
tls.resize(id + 1);
void **v = &tls[id];
...
return *v ? v : nullptr;
}
void **QThreadStorageData::set(void *p)
{
QThreadData *data = QThreadData::current();
if (!data) {
qWarning("QThreadStorage::set: QThreadStorage can only be used with threads started with QThread");
return nullptr;
}
QList<void *> &tls = data->tls;
if (tls.size() <= id)
tls.resize(id + 1);
void *&value = tls[id];
// delete any previous data
if (value != nullptr) {
...
QMutexLocker locker(&destructorsMutex);
DestructorMap *destr = destructors();
void (*destructor)(void *) = destr ? destr->value(id) : nullptr;
locker.unlock();
void *q = value;
value = nullptr;
if (destructor)
destructor(q);
}
// store new data
value = p;
...
return &value;
}
QThreadData 声明如下。它提供的静态方法 current() 是查找线程本地存储的入口。该方法会根据不同操作系统提供不同的实现。
// qtbase\src\corelib\thread\qthread_p.h
...
class QThreadData
{
public:
...
static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
...
private:
QAtomicInt _ref;
public:
int loopLevel;
int scopeLevel;
QStack<QEventLoop *> eventLoops;
QPostEventList postEventList;
QAtomicPointer<QThread> thread;
QAtomicPointer<void> threadId;
QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;
// 数据存储在此
QList<void *> tls;
bool quitNow;
bool canWait;
bool isAdopted;
bool requiresCoreApplication;
};
windows 系统中 current() 方法的实现如下。从源码可以看出只有一个 TLS 索引,QT 中多个 QThreadStorage 对象都对应一个 TLS 索引,多个 QThreadStorage 对象存储的值在 QThreadData 的 tls 列表中,QThreadStorageData 对象的 id 标记了 QThreadStorage 在 tls 中的位置。
// qtbase\src\corelib\thread\qthread_win.cpp
...
// TLS 索引
static DWORD qt_current_thread_data_tls_index = TLS_OUT_OF_INDEXES;
// 创建 TLS 索引
void qt_create_tls()
{
if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES)
return;
Q_CONSTINIT static QBasicMutex mutex;
QMutexLocker locker(&mutex);
if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES)
return;
// 仅分配一次 TLS 索引
qt_current_thread_data_tls_index = TlsAlloc();
}
...
QThreadData *QThreadData::current(bool createIfNecessary)
{
// 分配 TLS 索引
qt_create_tls();
// 该索引在线程中存储的数据类型为 QThreadData
QThreadData *threadData = reinterpret_cast<QThreadData *>(TlsGetValue(qt_current_thread_data_tls_index));
if (!threadData && createIfNecessary) {
// 在线程内新建 QThreadData 数据
threadData = new QThreadData;
// This needs to be called prior to new AdoptedThread() to
// avoid recursion.
TlsSetValue(qt_current_thread_data_tls_index, threadData);
QT_TRY {
threadData->thread.storeRelease(new QAdoptedThread(threadData));
} QT_CATCH(...) {
TlsSetValue(qt_current_thread_data_tls_index, 0);
threadData->deref();
threadData = 0;
QT_RETHROW;
}
threadData->deref();
threadData->isAdopted = true;
threadData->threadId.storeRelaxed(reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId())));
if (!QCoreApplicationPrivate::theMainThreadId) {
auto *mainThread = threadData->thread.loadRelaxed();
mainThread->setObjectName("Qt mainThread");
QCoreApplicationPrivate::theMainThread.storeRelease(mainThread);
QCoreApplicationPrivate::theMainThreadId.storeRelaxed(threadData->threadId.loadRelaxed());
} else {
HANDLE realHandle = INVALID_HANDLE_VALUE;
DuplicateHandle(GetCurrentProcess(),
GetCurrentThread(),
GetCurrentProcess(),
&realHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
qt_watch_adopted_thread(realHandle, threadData->thread.loadRelaxed());
}
}
return threadData;
}
Linux系统中 current() 方法的实现如下。与 windows 相同,只提供了一个 TSD 键,QT 中多个 QThreadStorage 对象都对应一个 TSD 键。currentThreadData 通过 thread_local 关键字保证了每个线程都有一个副本,省去了通过 pthread_getspecific() 与 pthread_setspecific() 访问数据。
// qtbase\src\corelib\thread\qthread_unix.cpp
...
// Always access this through the {get,set,clear}_thread_data() functions.
// currentThreadData 声明为 thread_local 类型,保证每个线程一个副本
Q_CONSTINIT static thread_local QThreadData *currentThreadData = nullptr;
...
// Utility functions for getting, setting and clearing thread specific data.
static QThreadData *get_thread_data()
{
return currentThreadData;
}
namespace {
struct PThreadTlsKey
{
pthread_key_t key;
// 创建 TSD 键
PThreadTlsKey() noexcept { pthread_key_create(&key, destroy_current_thread_data); }
~PThreadTlsKey() { pthread_key_delete(key); }
};
}
...
// TLS 索引
static PThreadTlsKey pthreadTlsKey; // intentional non-trivial init & destruction
static void set_thread_data(QThreadData *data) noexcept
{
if (data) {
// As noted above: one global static for the thread that called
// ::exit() (which may not be a Qt thread) and the pthread_key_t for
// all others.
static struct Cleanup {
~Cleanup() {
if (QThreadData *data = get_thread_data())
destroy_current_thread_data(data);
}
} currentThreadCleanup;
pthread_setspecific(pthreadTlsKey.key, data);
}
currentThreadData = data;
}
...
QThreadData *QThreadData::current(bool createIfNecessary)
{
QThreadData *data = get_thread_data();
if (!data && createIfNecessary) {
data = new QThreadData;
QT_TRY {
set_thread_data(data);
data->thread.storeRelease(new QAdoptedThread(data));
} QT_CATCH(...) {
clear_thread_data();
data->deref();
data = nullptr;
QT_RETHROW;
}
data->deref();
data->isAdopted = true;
data->threadId.storeRelaxed(QThread::currentThreadId());
if (!QCoreApplicationPrivate::theMainThreadId.loadAcquire()) {
auto *mainThread = data->thread.loadRelaxed();
mainThread->setObjectName("Qt mainThread");
QCoreApplicationPrivate::theMainThread.storeRelease(mainThread);
QCoreApplicationPrivate::theMainThreadId.storeRelaxed(data->threadId.loadRelaxed());
}
}
return data;
}
本内容都来自与项目 Compelling Data Designer开发过程中的思考与总结。
项目 Compelling Data Designer 用于数据的可视化设计,软件采用可扩展架构,支持扩展图形插件、数据接口。项目仍在开发中,目前已设计完成基本图形、多属性配置、动画、数据驱动等功能。
https://github.com/lsyeei/dashboard



浙公网安备 33010602011771号