线程私有数据(TSD & TLS)
线程私有数据
线程私有数据(又叫做线程特定数据(Thread-Specific Data, TSD)或者线程局部数据(Thread-Local Storage,TLS) ) 是指在多线程编程中,每个线程都拥有自己独立的数据空间,这些数据在不同线程之间是相互隔离的,不会被其他线程访问或修改。这种机制可以帮助解决多线程并发执行时的数据共享和竞争问题。
在许多编程语言和操作系统中,都提供了一些机制来实现线程私有数据,常见的方法有:
- 线程局部存储(Thread-Local Storage,TLS): 这是一种在每个线程中分配一个独立的存储区域,使得每个线程可以将数据存储在这个区域内,而不会被其他线程访问。不同编程语言和库提供了不同的方式来实现 TLS,如C++中的thread_local关键字。
- 栈: 在一些编程语言中,每个线程都拥有自己的栈,栈上分配的局部变量是线程私有的,不会被其他线程访问。
- 线程专有数据(Thread-Private Data): 一些编程环境中,如OpenMP并行编程框架,提供了专门的方式来声明线程私有数据,这些数据只在声明它们的线程中可见。
Windows 系统如何实现 TLS
在Windows操作系统中,线程私有数据(Thread-Local Storage,TLS)的实现是通过使用TLS回调函数和特殊的TLS索引来完成的。
简单看一个列子:
#include <windows.h>
// 定义一个TLS索引
static DWORD tlsIndex;
// 定义TLS回调函数
void WINAPI MyTlsCallback(PVOID hModule, DWORD dwReason, PVOID pReserved) {
switch (dwReason) {
case DLL_THREAD_ATTACH:
// 初始化线程私有数据
TlsSetValue(tlsIndex, (LPVOID)some_thread_data);
break;
case DLL_THREAD_DETACH:
// 释放线程私有数据
// 注意:不要在此函数中进行内存分配/释放操作
break;
case DLL_PROCESS_ATTACH:
case DLL_PROCESS_DETACH:
break;
}
}
int main() {
// 分配一个TLS索引
tlsIndex = TlsAlloc();
if (tlsIndex == TLS_OUT_OF_INDEXES) {
// 处理错误
return 1;
}
// 将TLS回调函数关联到TLS索引上
if (!TlsSetValue(tlsIndex, NULL)) {
// 处理错误
TlsFree(tlsIndex);
return 1;
}
// 注册TLS回调函数
if (!DllMain(GetModuleHandle(NULL), DLL_THREAD_ATTACH, NULL)) {
// 处理错误
TlsFree(tlsIndex);
return 1;
}
// 程序逻辑
// 释放TLS索引
TlsFree(tlsIndex);
return 0;
}
在这个示例中,MyTlsCallback函数是一个TLS回调函数,用于初始化和释放线程私有数据。main函数中分配了一个TLS索引,将TLS回调函数关联到该索引上,并在需要的时候通过TlsSetValue进行数据初始化。最后,在程序结束前,需要通过TlsFree释放分配的TLS索引。
Windows 可使用FLS实现TLS
在Windows操作系统中,除了TLS(Thread-Local Storage)之外,还存在Fiber-Local Storage(FLS,纤程局部存储),这是一种类似的机制,但是它与纤程(Fiber)相关联,而不是线程。
Fiber是一种用户态的轻量级执行单元,类似于线程,但是由用户应用程序管理。纤程能够更精细地控制调度和上下文切换。与线程类似,纤程也可能需要在不同的执行上下文中维护私有数据。
FlsAlloc函数用于在纤程中分配一个Fiber-Local Storage(FLS)索引。与线程局部存储(TLS)类似,FLS也允许每个纤程拥有自己的私有数据区域,这些数据在不同纤程之间是隔离的。
以下是一个简化的示例代码,演示如何在Windows中使用FlsAlloc分配纤程局部存储索引:
#include <windows.h>
// 纤程局部存储索引
static DWORD flsIndex;
// 纤程入口函数
VOID WINAPI FiberStart(LPVOID lpFiberParameter) {
// 获取纤程局部存储中的数据
LPVOID data = FlsGetValue(flsIndex);
// 在这里可以使用纤程私有数据
}
int main() {
// 分配一个纤程局部存储索引
flsIndex = FlsAlloc(NULL);
if (flsIndex == FLS_OUT_OF_INDEXES) {
// 处理错误
return 1;
}
// 创建纤程
LPVOID fiber = CreateFiber(0, FiberStart, NULL);
if (fiber == NULL) {
// 处理错误
FlsFree(flsIndex);
return 1;
}
// 设置纤程局部存储中的数据
FlsSetValue(flsIndex, (LPVOID)some_fiber_data);
// 切换到纤程
SwitchToFiber(fiber);
// 释放纤程和纤程局部存储索引
DeleteFiber(fiber);
FlsFree(flsIndex);
return 0;
}
在这个示例中,我们使用了FlsAlloc来分配一个FLS索引,然后在纤程中使用FlsSetValue设置纤程局部存储中的数据。在纤程入口函数FiberStart中,可以使用FlsGetValue获取纤程局部存储中的数据。
需要注意的是,纤程的使用在大多数应用程序中相对较少,因为线程通常更为常见。纤程在特定的情况下可能有用,但需要谨慎使用,因为它们引入了更复杂的编程模型和管理。
macOS 或者 Linux 系统如何实现 TLS
在 macOS 和 Linux 等类 Unix 系统中,线程局部存储(TLS,Thread-Local Storage)的实现也存在,但与 Windows 有一些差异。可以使用 POSIX 线程库中的 pthread_key_create、pthread_key_delete、pthread_setspecific 和 pthread_getspecific 等函数来实现线程局部存储。
#include <pthread.h>
// 定义线程局部存储键
static pthread_key_t tls_key;
// 线程局部存储析构函数
void tls_cleanup(void *value) {
// 在线程退出时调用,用于释放线程局部存储中的数据
// 这里可以进行资源的清理工作
free(value);
}
void *thread_function(void *arg) {
// 设置线程局部存储数据
int *data = malloc(sizeof(int));
*data = 42;
pthread_setspecific(tls_key, data);
// 线程逻辑
return NULL;
}
int main() {
// 创建线程局部存储键
pthread_key_create(&tls_key, tls_cleanup);
// 创建线程
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
// 主线程逻辑
// 等待线程结束
pthread_join(thread, NULL);
// 删除线程局部存储键
pthread_key_delete(tls_key);
return 0;
}
C++11中如何实现TLS
在 C++11 以及后续的标准中,可以使用 thread_local 关键字来实现线程局部存储(TLS)。这个关键字用于声明线程局部存储变量,每个线程都会拥有自己的变量副本,从而避免了多线程并发访问的竞争条件问题。
以下是一个示例,演示了在 C++11 中如何使用 thread_local 关键字来实现线程局部存储:
#include <iostream>
#include <thread>
// 声明线程局部存储变量
thread_local int thread_private_data = 0;
void thread_function() {
// 设置线程局部存储变量的值
thread_private_data = std::this_thread::get_id();
// 打印线程局部存储变量的值
std::cout << "Thread ID: " << thread_private_data << std::endl;
}
int main() {
// 创建线程并启动
std::thread thread1(thread_function);
std::thread thread2(thread_function);
// 等待线程完成
thread1.join();
thread2.join();
return 0;
}
在这个示例中,thread_private_data 就是一个线程局部存储变量,通过 thread_local 关键字声明。每个线程都有自己的副本,线程之间的访问是隔离的。
C++11 引入了 thread_local 关键字,使得实现线程局部存储变得更加简便。但是请注意,thread_local 关键字的使用需要编译器和标准库支持 C++11 或更高版本。大多数现代编译器都支持这个特性,但仍然建议在使用时查阅相应的文档以确保兼容性。
本文系作者 @何健源 原创发布在思维代码站点。未经许可,禁止转载。
暂无评论数据