TLS(线程局部存储)

TLS全称线程局部存储器,它用来保存变量或回调函数。  

TLS里面的变量和回调函数都在程序入口点(AddressOfEntry)之前执行,也就是说程序在被调试时,还没有在入口点处断下来之前,TLS中的变量和回调函数就已经执行完了,所以TLS可以用作反调试之类的操作。

  要在程序中使用TLS,必须为TLS数据单独建一个数据段,用相关数据填充此段,并通知链接器为TLS数据在PE文件头中添加数据。

__declspec (thread)int g_nNum = 0x9597;
__declspec (thread)char g_szStr[] = "TLS g_nNum = 0x%p ...\r\n";

void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Red)
{
	if (DLL_PROCESS_ATTACH == Reason)
	{
		printf("t_TlsCallBack_A -> ThreadDetach!\r\n");
		return;
	}
}
void NTAPI t_TlsCallBack_B(PVOID DllHandle, DWORD Reason, PVOID Red)
{
	if (DLL_PROCESS_ATTACH == Reason)
	{
		printf("t_TlsCallBack_B -> ThreadDetach!\r\n");
		return;
	}
}

#pragma  data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK p_thread_callback[] = {
	t_TlsCallBack_A,
	t_TlsCallBack_B,
	NULL
};
#pragma  data_seg()

  上述代码中,创建TLS段的部分“.CRT$XLX”的含义如下:.CRT表明是使用C RunTime机制,$后面的XLX中:X表示随机的标识,L表示是TLS callback section,X可以被换成B到Y的任意一个字母,但是不能使用“.CRT$XLA”和“.CRT$XLZ”。

  其中p_thread_callback数组中保存了所有的TLS回调函数指针。值得指出的是,数组必须以NULL指针结束,且数组中的每一个回调函数在程序初始化时都会被调用,程序员可按需要添加。但程序员不应当假设操作系统已何种顺序调用回调函数。在实际测试中,回调函数是依次被调用的。

  TLS回调函数中第二个参数决定了函数在那种情况下(DllMain函数一样)被调用:

  DLL_PROCESS_ATTACH:是指新进程创建时,初始化主线程时执行。

  DLL_PROCESS_DETACH: 是指在进程终止时执行。

  DLL_THREAD_ATTACH:是指创建新线程时,但是不包括主线程。

  DLL_THREAD_DETACH:是指在所有线程终止时执行,但是同样不包括主线程。

  需要指出的是,在TLS回调函数执行时,VC运行库msvcrt.dll,mfc.dll等并未载入,不能使用C库的函数(比如printf)。如果有需要使用,应该使用LoadLibrary()函数载入相应的库并使用GetProcAddress()获得函数地址。但此类操作可能会导致调试器的相关事件触发,不建议进行此类操作。

 

再来看看PE文件中的TLS表,结构如下

typedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD   StartAddressOfRawData;    TLS初始化数据的起始地址
    DWORD   EndAddressOfRawData;      TLS初始化数据的结束地址  两个正好定位一个范围,范围放初始化的值
    DWORD   AddressOfIndex;              TLS 索引的位置
    DWORD   AddressOfCallBacks;          Tls回调函数的数组指针
    DWORD   SizeOfZeroFill;         填充0的个数
    union {
        DWORD Characteristics;      保留
        struct {
            DWORD Reserved0 : 20;
            DWORD Alignment : 4;
            DWORD Reserved1 : 8;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;

} IMAGE_TLS_DIRECTORY32;

 

<think>嗯,我现在需要了解TLS线程局部存储)在C++中的使用方法,以及如何用TLS实现无锁队列。首先,我得先理解什么是线程局部存储。根据之前学的多线程知识,线程之间共享进程的资源,但有时候每个线程需要有自己的数据副本,避免竞争条件。TLS可能就是用来实现这个的。比如,每个线程可能需要独立的计数器或者临时缓冲区。 那在C++中,TLS是怎么使用的呢?我记得C++11引入了thread_local关键字,可能和这个有关。比如用thread_local声明变量,每个线程都会有自己独立的实例。另外,Windows和Linux可能有自己的API,比如Windows的TlsAlloc、TlsGetValue这些函数,不过现在可能更推荐使用语言层面的thread_local。 接下来,用户还提到用TLS实现无锁队列。无锁队列通常通过原子操作实现,避免互斥锁的开销。但如何结合TLS呢?也许每个线程通过TLS维护自己的数据,比如本地队列,然后通过某种方式合并到全局队列中。这样线程在操作自己的本地队列时不需要锁,只在需要与全局队列交互时处理同步问题。比如,写入时线程先放入自己的TLS队列,批量转移时用原子操作更新全局队列。这样可以减少竞争,但需要考虑数据一致性和内存屏障的问题。 不过,可能存在一些误区。比如,TLS本身并不是用来解决线程间通信的,而是线程内的数据隔离。无锁队列的重点是原子操作和内存顺序,而结合TLS可能是为了减少同步的频率,比如每个线程处理自己的部分,最后合并。需要确认这种设计是否可行,以及潜在的问题,比如延迟、数据丢失等。 需要进一步查阅资料,确认TLS的正确用法,以及在无锁结构中应用的案例。比如,是否可以将每个线程的本地缓存通过TLS管理,然后定期无锁地合并到全局队列,这样减少全局队列的竞争。具体实现可能需要双缓冲技术或者CAS(比较并交换)操作来确保线程安全。 总结思路:首先介绍TLS的概念和在C++中的使用方法,包括thread_local关键字和平台相关API。然后讨论如何利用TLS为每个线程维护本地队列,减少对全局队列的竞争,通过原子操作将本地队列的数据合并到全局队列,从而实现无锁的高效队列结构。需要注意内存可见性和原子操作的顺序,确保数据正确同步。</think>### TLS线程局部存储介绍 #### 1. 基本概念 线程局部存储(Thread Local Storage, TLS)是一种允许每个线程**独立存储和访问数据副本**的机制。在多线程环境中,普通全局变量是共享的,而TLS变量会为每个线程创建独立的实例,避免多线程竞争。 #### 2. C++中的TLS实现 - **C++11标准**:通过`thread_local`关键字声明变量 ```cpp thread_local int perThreadData; // 每个线程独立实例 ``` - **Windows API**(兼容旧代码): ```cpp DWORD tlsIndex = TlsAlloc(); // 创建TLSTlsSetValue(tlsIndex, dataPtr); // 存储数据 void* data = TlsGetValue(tlsIndex); // 读取数据 ``` - **Linux/POSIX**: ```cpp pthread_key_t key; pthread_key_create(&key, destructor); // 创建TLS键 pthread_setspecific(key, dataPtr); // 存储数据 void* data = pthread_getspecific(key); // 读取数据 ``` --- ### 用TLS实现无锁队列的核心思路 #### 1. 设计目标 - **减少全局竞争**:通过TLS为每个线程维护**本地队列**,避免直接操作全局队列 - **批量提交**:将多次操作合并后,通过原子操作更新到全局队列 #### 2. 实现步骤 ```cpp // 全局无锁队列(示例使用原子标记指针) std::atomic<Node*> globalHead{nullptr}; // 每个线程TLS本地队列 thread_local std::vector<Data> localQueue; void enqueue(const Data& data) { localQueue.push_back(data); // 批量提交到全局队列(阈值示例:100条) if (localQueue.size() >= 100) { // 1. 创建新节点 Node* newNode = createBulkNode(localQueue); // 2. 原子操作更新全局队列 Node* oldHead = globalHead.load(std::memory_order_relaxed); do { newNode->next = oldHead; } while (!globalHead.compare_exchange_weak( oldHead, newNode, std::memory_order_release, std::memory_order_relaxed)); // 3. 清空本地队列 localQueue.clear(); } } ``` #### 3. 关键技术点 - **内存顺序选择**:`std::memory_order_release`确保本地数据对其他线程可见 - **CAS原子操作**:`compare_exchange_weak`实现无锁的头部更新 - **批量合并策略**:通过阈值控制同步频率(空间换时间) --- ### 注意事项 1. **数据可见性**:最后一次提交需确保内存同步 2. **延迟处理**:未达阈值的残留数据需特殊处理(如析构时强制提交) 3. **负载均衡**:消费线程需处理全局队列合并后的数据 4. **ABA问题**:需通过标记指针或风险指针(Hazard Pointer)避免 --- ### 典型应用场景 - 高频日志系统(每个线程缓存日志,批量写入) - 事件处理器(线程本地收集事件,定时刷新) - 对象池分配器(本地缓存对象,减少全局锁竞争) ### 性能对比 | 方案 | 锁竞争频率 | 吞吐量 | 延迟一致性 | |-----------------|------------|---------|------------| | 传统互斥锁队列 | 每次操作 | 低 | 高 | | 纯无锁队列 | 每次操作 | 中 | 中 | | TLS+无锁队列 | 按批次 | 高 | 低 | (注:实际性能需结合具体场景测试)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值