线程私有数据(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 或更高版本。大多数现代编译器都支持这个特性,但仍然建议在使用时查阅相应的文档以确保兼容性。

分类: 音视频开发扫盲 标签: CPPCPP 11TLSTSDThread

评论

暂无评论数据

暂无评论数据

目录