线程局部存储及thread_local说明符

发布于:2025-02-26 ⋅ 阅读:(219) ⋅ 点赞:(0)

线程局部存储

线程局部存储是指对象内存在线程开始后分配,线程结束后回收;且每个线程都有该对象自己的实例。

线程本身是操作系统的概念,线程局部存储这个功能离不开操作系统支持。不同操作系统对线程局部存储的实现有不同,使用的系统 API 也有区别。

1. Windows原生API实现

#include <windows.h>

DWORD g_tlsIndex; // TLS槽索引

// 初始化TLS系统
void InitTLS() {
    g_tlsIndex = TlsAlloc(); // 分配TLS索引
}

// 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParam) {
    int* pData = new int(42);
    TlsSetValue(g_tlsIndex, pData); // 设置线程私有数据

    int* ret = (int*)TlsGetValue(g_tlsIndex); // 获取数据
    // 使用数据...

    delete pData;
    return 0;
}

// 清理资源
void Cleanup() {
    TlsFree(g_tlsIndex);
}

2. Linux pthread实现

#include <pthread.h>

pthread_key_t g_key; // TLS键

// 析构函数(线程退出时自动调用)
void Destructor(void* data) {
    delete static_cast<int*>(data);
}

void InitPthreadTLS() {
    pthread_key_create(&g_key, Destructor); // 创建带析构的键
}

void* ThreadProc(void* arg) {
    int* pData = new int(42);
    pthread_setspecific(g_key, pData); // 设置数据

    int* ret = static_cast<int*>(pthread_getspecific(g_key));
    // 使用数据...
    
    return nullptr;
}

void CleanupPthread() {
    pthread_key_delete(g_key);
}

3. C++11标准化实现

基础声明方式

thread_local int g_tlsVar = 0; // 外部链接

struct Widget {
    static thread_local std::string s_cache; // 类静态成员
};
thread_local std::string Widget::s_cache = "init"; // 类外定义

void ThreadTask() {
    thread_local std::vector<int> localVec; // 自动初始化
    localVec.push_back(42);
    // 线程退出时自动析构
}

初始化特性验证

struct Tracer {
    Tracer() { std::cout << "构造于" << std::this_thread::get_id() << '\n'; }
    ~Tracer() { std::cout << "析构于" << std::this_thread::get_id() << '\n'; }
};

void TestInit() {
    thread_local Tracer t; // 每个线程首次调用时构造
}

// 启动3个线程各调用TestInit()两次
// 输出结果:每个线程只输出一次构造/析构

关键特性验证代码

a. 地址运行时计算验证
thread_local int tlsVar;
static int staticVar;

int main() {
    constexpr const int* pStatic = &staticVar; // 编译期确定
    // constexpr const int* pTls = &tlsVar; // 编译错误!地址运行时确定
    std::cout << "TLS地址:" << &tlsVar << '\n'; // 每次运行不同
}
b. 跨线程访问风险演示
thread_local std::string tlsStr;

void MaliciousThread() {
    std::string* p = &tlsStr; // 获取TLS变量地址
    // 将p传递给其他线程...
}

void AttackThread(std::string* p) {
    // 当原线程退出后访问:
    p->append("boom"); // 未定义行为!可能崩溃
}

典型应用场景

a. 线程安全版errno实现
thread_local int tls_errno = 0;

int thread_safe_api() {
    if(failure) {
        tls_errno = EAGAIN; // 仅影响当前线程
        return -1;
    }
    return 0;
}

void ThreadWorker() {
    if(thread_safe_api() == -1) {
        std::cout << "线程" << std::this_thread::get_id() 
                  << "错误码:" << tls_errno << '\n';
    }
}
b. 线程局部缓存池
class ThreadCache {
public:
    static thread_local ThreadCache instance;
    
    void* Alloc(size_t size) { /* ... */ }
    void Dealloc(void* ptr) { /* ... */ }

private:
    ThreadCache() = default; // 禁止外部构造
    std::vector<void*> blocks_;
};

thread_local ThreadCache ThreadCache::instance; // 每个线程独立实例

void ProcessRequest() {
    void* mem = ThreadCache::instance.Alloc(1024);
    // 使用内存...
    ThreadCache::instance.Dealloc(mem);
}
各平台历史实现对比
平台 关键字/API 特性
GCC __thread 基础类型支持,无析构函数
Clang __thread 同GCC
MSVC __declspec(thread) 支持类类型,但有构造限制
C++11 thread_local 完整对象生命周期管理
Windows TlsAlloc/TlsSetValue 手动内存管理,最大支持1088个索引
Linux pthread_key_create 带析构函数,默认1024个键

thread_local

#include <iostream>
#include <string>
#include <thread>
#include <mutex>

// 输出同步互斥锁(确保多线程下的输出完整性)
std::mutex g_out_lock;

/// @brief 引用计数器(演示thread_local特性)
struct RefCount {
    // 构造函数:记录创建时的线程和函数信息
    RefCount(const std::string& f) // 改为直接接收string避免悬垂指针
        : i(0), func(f) {
        std::lock_guard<std::mutex> lock(g_out_lock);
        std::cout << std::this_thread::get_id() 
                  << "|" << func
                  << ": 构造函数 i(" << i << ")\n";
    }

    // 析构函数:展示对象生命周期结束
    ~RefCount() {
        std::lock_guard<std::mutex> lock(g_out_lock);
        std::cout << std::this_thread::get_id() 
                  << "|" << func 
                  << ": 析构函数 i(" << i << ")\n";
    }

    // 增加引用计数并打印日志
    void inc() {
        std::lock_guard<std::mutex> lock(g_out_lock);
        std::cout << std::this_thread::get_id() 
                  << "|" << func 
                  << ": 引用计数+1 i(" << i << "->" << i+1 << ")\n";
        ++i;
    }

    int i;
    std::string func;  // 使用string副本避免指针失效
};

// 线程局部存储测试函数
void foo(const char* f) {
    // 先构造完整字符串再传递(确保生命周期)
    const std::string func = std::string(f) + "#foo";
    thread_local RefCount tv(func);  // 每个线程独享的静态对象
    tv.inc();
}

void bar(const char* f) {
    const std::string func = std::string(f) + "#bar";
    thread_local RefCount tv(func);
    tv.inc();
}

// 测试线程1:连续调用foo
void threadfunc1() {
    const char* func = "threadfunc1";
    foo(func);  // 首次调用初始化thread_local
    foo(func);  // 后续调用复用对象
    foo(func);
}

// 测试线程2:与线程1相同模式
void threadfunc2() {
    const char* func = "threadfunc2";
    foo(func);
    foo(func);
    foo(func);
}

// 测试线程3:混合调用foo和bar
void threadfunc3() {
    const char* func = "threadfunc3";
    foo(func);  // 初始化foo的thread_local
    bar(func);  // 初始化bar的thread_local
    bar(func);  // 复用bar的对象
}

int main() {
    std::thread t1(threadfunc1);
    std::thread t2(threadfunc2);
    std::thread t3(threadfunc3);

    t1.join();
    t2.join();
    t3.join();
}

代码行为解释

  1. thread_local特性演示

    thread_local RefCount tv(...);
    
    • 每个线程首次访问时初始化
    • 线程结束时自动销毁
    • 不同线程拥有独立实例
  2. 典型输出示例
    在这里插入图片描述

  3. 线程执行模式

    • 线程1/2:连续调用foo,共3次inc()
    • 线程3:调用foo(1次) + bar(2次)
    • 每个thread_local对象在对应函数首次调用时创建

关键概念说明

特性 说明
thread_local 线程局部存储,每个线程有独立实例,首次访问时构造,线程退出时销毁
lock_guard RAII锁管理,确保离开作用域时自动释放互斥锁
构造函数/析构函数 展示thread_local对象的生命周期管理
引用计数 通过inc()方法演示对象被重复使用的状态变化

推荐一下

https://github.com/0voice


网站公告

今日签到

点亮在社区的每一天
去签到