跳转到内容

C 语言线程局部存储:修订间差异

来自代码酷
Admin留言 | 贡献
Page creation by admin bot
 
Admin留言 | 贡献
Page update by admin bot
 
第1行: 第1行:
{{DISPLAYTITLE:C语言线程局部存储}}
= C语言线程局部存储 =


== 简介 ==
线程局部存储(Thread Local Storage, TLS)是C语言中一种允许每个线程拥有自己独立变量副本的机制。在多线程编程中,全局变量和静态变量默认是被所有线程共享的,而使用TLS可以创建线程私有的数据,避免竞争条件并简化线程同步。
'''线程局部存储'''(Thread-Local Storage, TLS)是C语言中一种允许每个线程拥有独立变量副本的机制。在多线程编程中,全局变量和静态变量通常被所有线程共享,而TLS提供了一种为每个线程创建私有数据的方式,避免了线程间的数据竞争问题。


TLS的主要特点包括:
== 基本概念 ==
* 每个线程拥有变量的独立副本
* 变量的生命周期与线程绑定
* 适用于需要线程特定数据的场景
* 在POSIX系统和Windows系统中有不同的实现方式


== 实现方式 ==
在C11标准之前,TLS通过编译器扩展(如GCC的<code>__thread</code>或MSVC的<code>__declspec(thread)</code>)实现。C11标准正式引入了<code>_Thread_local</code>关键字(可用宏<code>thread_local</code>替代)来支持线程局部存储。
C语言中实现线程局部存储主要有两种方式:


=== C11标准中的 _Thread_local ===
线程局部存储的变量:
C11标准引入了<code>_Thread_local</code>存储类说明符(可通过<code>threads.h</code>头文件使用):
* 每个线程拥有独立的副本
* 生命周期与线程相同
* 初始化仅在首次访问时进行(对于静态存储期变量)
 
== 语法与声明 ==
 
声明线程局部变量的语法如下:


<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
#include <threads.h>
#include <threads.h>
#include <stdio.h>


_Thread_local int tls_var; // 每个线程都有独立的tls_var副本
thread_local int tls_var; // 文件作用域的TLS变量
 
int thread_func(void *arg) {
    tls_var = *(int*)arg;
    printf("Thread %ld: tls_var = %d\n", thrd_current(), tls_var);
    return 0;
}


int main() {
void func() {
     thrd_t t1, t2;
     static thread_local int static_tls; // 静态存储期的TLS变量
     int arg1 = 10, arg2 = 20;
     thread_local int local_tls; // 错误:不能声明自动存储期的TLS变量
   
    thrd_create(&t1, thread_func, &arg1);
    thrd_create(&t2, thread_func, &arg2);
   
    thrd_join(t1, NULL);
    thrd_join(t2, NULL);
   
    return 0;
}
}
</syntaxhighlight>
</syntaxhighlight>


可能的输出:
== 代码示例 ==
<pre>
Thread 1: tls_var = 10
Thread 2: tls_var = 20
</pre>


=== POSIX线程的pthread_key_t ===
以下示例展示TLS变量的基本用法:
在POSIX线程中,可以使用<code>pthread_key_t</code>和相关函数:


<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
#include <pthread.h>
#include <stdio.h>
#include <stdio.h>
#include <threads.h>
#include <stdlib.h>


pthread_key_t key;
thread_local int counter = 0; // 每个线程有自己的counter副本


void destructor(void *value) {
int thread_func(void *arg) {
    printf("Destroying thread-specific data: %d\n", *(int*)value);
     counter++; // 修改线程本地的counter
    free(value);
     printf("Thread %ld: counter = %d\n", (long)arg, counter);
}
     return 0;
 
void *thread_func(void *arg) {
     int *data = malloc(sizeof(int));
    *data = *(int*)arg;
    pthread_setspecific(key, data);
     printf("Thread %lu: data = %d\n", pthread_self(), *(int*)pthread_getspecific(key));
     return NULL;
}
}


int main() {
int main() {
     pthread_t t1, t2;
     thrd_t t1, t2;
    int arg1 = 100, arg2 = 200;
      
      
     pthread_key_create(&key, destructor);
     thrd_create(&t1, thread_func, (void *)1);
    thrd_create(&t2, thread_func, (void *)2);
      
      
     pthread_create(&t1, NULL, thread_func, &arg1);
     thrd_join(t1, NULL);
     pthread_create(&t2, NULL, thread_func, &arg2);
     thrd_join(t2, NULL);
      
      
     pthread_join(t1, NULL);
     printf("Main thread: counter = %d\n", counter);
    pthread_join(t2, NULL);
   
    pthread_key_delete(key);
     return 0;
     return 0;
}
}
</syntaxhighlight>
</syntaxhighlight>


可能的输出:
'''可能的输出:'''
<pre>
<pre>
Thread 12345678: data = 100
Thread 1: counter = 1
Thread 87654321: data = 200
Thread 2: counter = 1
Destroying thread-specific data: 100
Main thread: counter = 0
Destroying thread-specific data: 200
</pre>
</pre>


== 工作原理 ==
== 实现原理 ==
线程局部存储的实现通常依赖于以下机制:
 
TLS的实现通常依赖于操作系统和编译器的协作:


<mermaid>
<mermaid>
graph TD
graph TD
     A[主线程] -->|创建| B[线程1]
     A[程序启动] --> B[为TLS变量分配存储空间]
     A -->|创建| C[线程2]
     B --> C[主线程初始化]
     B --> D[线程1的TLS区域]
     C --> D[创建新线程]
     C --> E[线程2的TLS区域]
     D --> E[为新线程分配独立的TLS存储]
     D --> F[变量副本1]
     E --> F[线程访问自己的TLS副本]
    E --> G[变量副本2]
</mermaid>
</mermaid>


* 每个线程都有独立的TLS存储区域
== 实际应用场景 ==
* 编译器/运行时系统维护TLS变量的访问
* 访问TLS变量比访问普通变量稍慢


== 实际应用案例 ==
1. '''错误码存储''':如C库的<code>errno</code>通常实现为TLS变量
=== 案例1:线程特定的错误码 ===
2. '''线程特定缓存''':避免锁竞争
在多线程程序中,使用全局变量存储错误码会导致竞争条件,TLS提供了完美解决方案:
3. '''随机数生成器状态''':保持每个线程的独立状态
 
<syntaxhighlight lang="c">
#include <pthread.h>
#include <stdio.h>
#include <errno.h>


pthread_key_t errno_key;
== 高级主题:动态TLS ==


void *thread_func(void *arg) {
除了语言级别的TLS支持,操作系统还提供API级别的TLS:
    int *thread_errno = malloc(sizeof(int));
    *thread_errno = 0;
    pthread_setspecific(errno_key, thread_errno);
   
    // 模拟错误发生
    *thread_errno = EACCES;
    printf("Thread %lu error: %d\n", pthread_self(), *(int*)pthread_getspecific(errno_key));
   
    return NULL;
}


int main() {
* Windows: <code>TlsAlloc()</code>, <code>TlsGetValue()</code>, <code>TlsSetValue()</code>
    pthread_t t1, t2;
* POSIX: <code>pthread_key_create()</code>, <code>pthread_getspecific()</code>, <code>pthread_setspecific()</code>
   
    pthread_key_create(&errno_key, free);
   
    pthread_create(&t1, NULL, thread_func, NULL);
    pthread_create(&t2, NULL, thread_func, NULL);
   
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
   
    pthread_key_delete(errno_key);
    return 0;
}
</syntaxhighlight>


=== 案例2:线程特定的随机数种子 ===
示例(POSIX):
随机数生成器通常需要线程特定的种子以避免重复序列:


<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
#include <stdlib.h>
#include <pthread.h>
#include <pthread.h>
#include <stdio.h>
#include <stdio.h>
#include <time.h>


_Thread_local unsigned int seed;
pthread_key_t key;
 
void destructor(void *value) {
    free(value); // 清理线程特定数据
}


void *thread_func(void *arg) {
void *thread_func(void *arg) {
     seed = time(NULL) ^ pthread_self();
     int *data = malloc(sizeof(int));
      
    *data = pthread_self(); // 使用线程ID作为示例数据
    for (int i = 0; i < 3; i++) {
     pthread_setspecific(key, data);
        printf("Thread %lu random: %d\n", pthread_self(), rand_r(&seed));
    printf("Thread %lu: data = %d\n", pthread_self(), *(int *)pthread_getspecific(key));
    }
   
     return NULL;
     return NULL;
}
}


int main() {
int main() {
    pthread_key_create(&key, destructor);
     pthread_t t1, t2;
     pthread_t t1, t2;
      
      
第181行: 第121行:
     pthread_join(t2, NULL);
     pthread_join(t2, NULL);
      
      
    pthread_key_delete(key);
     return 0;
     return 0;
}
}
第186行: 第127行:


== 性能考量 ==
== 性能考量 ==
使用线程局部存储需要注意以下性能特点:
* TLS访问比普通变量访问慢2-3倍
* 创建和销毁TLS键有一定开销
* 适合读写比例较低的场景


性能对比公式:
TLS访问通常比普通变量访问慢,因为:
* 需要通过额外的内存引用(如通过线程环境块)
* 某些架构需要特殊的指令支持
 
性能模型可以表示为:
<math>
<math>
T_{access} = T_{normal} + \Delta_{TLS}
T_{access} = T_{base} + T_{tls\_overhead}
</math>
</math>


其中:
== 限制与注意事项 ==
* <math>T_{access}</math>是TLS访问时间
* <math>T_{normal}</math>是普通变量访问时间
* <math>\Delta_{TLS}</math>是TLS额外开销


== 最佳实践 ==
1. TLS变量不能是自动存储期(不能声明在函数内非static)
1. 仅在真正需要线程特定数据时使用TLS
2. 动态库中的TLS可能有平台特定的行为
2. 避免在性能关键路径上频繁访问TLS变量
3. 大量使用TLS可能增加线程创建开销
3. 记得为动态分配的TLS数据提供析构函数
4. 某些嵌入式平台可能不支持TLS
4. 优先使用C11标准的<code>_Thread_local</code>(如果编译器支持)
5. 注意不同平台的实现差异


== 常见问题 ==
== 总结 ==
=== Q: TLS变量可以被其他线程访问吗? ===
A: 不能直接访问。TLS变量是线程私有的,其他线程无法直接访问另一个线程的TLS变量。
 
=== Q: TLS变量和局部变量有什么区别? ===
A: 局部变量只在函数调用期间存在,而TLS变量的生命周期与线程相同。局部变量在栈上分配,TLS变量在特殊的线程存储区域分配。


=== Q: 什么时候应该使用TLS? ===
线程局部存储是多线程编程中的重要工具,它:
A: 当需要:
* 提供线程隔离的数据存储
* 维护线程特定的状态信息
* 避免不必要的同步开销
* 避免传递大量参数给线程函数
* 简化线程安全代码的编写
* 替换不安全的使用全局变量场景
* 有语言级别和API级别两种实现方式


== 总结 ==
正确使用TLS可以显著提高多线程程序的可靠性和性能。
线程局部存储是多线程编程中的重要概念,它允许每个线程拥有变量的独立副本,避免了共享数据带来的同步问题。C语言提供了多种实现TLS的方式,开发者应根据具体需求和平台支持选择合适的实现方法。正确使用TLS可以显著提高多线程程序的可靠性和可维护性。


[[Category:编程语言]]
[[Category:编程语言]]
[[Category:C]]
[[Category:C]]
[[Category:C 语言多线程与并发]]
[[Category:C 语言高级特性]]

2025年4月29日 (二) 04:47的最新版本

C语言线程局部存储[编辑 | 编辑源代码]

线程局部存储(Thread Local Storage, TLS)是C语言中一种允许每个线程拥有自己独立变量副本的机制。在多线程编程中,全局变量和静态变量默认是被所有线程共享的,而使用TLS可以创建线程私有的数据,避免竞争条件并简化线程同步。

基本概念[编辑 | 编辑源代码]

在C11标准之前,TLS通过编译器扩展(如GCC的__thread或MSVC的__declspec(thread))实现。C11标准正式引入了_Thread_local关键字(可用宏thread_local替代)来支持线程局部存储。

线程局部存储的变量:

  • 每个线程拥有独立的副本
  • 生命周期与线程相同
  • 初始化仅在首次访问时进行(对于静态存储期变量)

语法与声明[编辑 | 编辑源代码]

声明线程局部变量的语法如下:

#include <threads.h>

thread_local int tls_var; // 文件作用域的TLS变量

void func() {
    static thread_local int static_tls; // 静态存储期的TLS变量
    thread_local int local_tls; // 错误:不能声明自动存储期的TLS变量
}

代码示例[编辑 | 编辑源代码]

以下示例展示TLS变量的基本用法:

#include <stdio.h>
#include <threads.h>
#include <stdlib.h>

thread_local int counter = 0; // 每个线程有自己的counter副本

int thread_func(void *arg) {
    counter++; // 修改线程本地的counter
    printf("Thread %ld: counter = %d\n", (long)arg, counter);
    return 0;
}

int main() {
    thrd_t t1, t2;
    
    thrd_create(&t1, thread_func, (void *)1);
    thrd_create(&t2, thread_func, (void *)2);
    
    thrd_join(t1, NULL);
    thrd_join(t2, NULL);
    
    printf("Main thread: counter = %d\n", counter);
    return 0;
}

可能的输出:

Thread 1: counter = 1
Thread 2: counter = 1
Main thread: counter = 0

实现原理[编辑 | 编辑源代码]

TLS的实现通常依赖于操作系统和编译器的协作:

graph TD A[程序启动] --> B[为TLS变量分配存储空间] B --> C[主线程初始化] C --> D[创建新线程] D --> E[为新线程分配独立的TLS存储] E --> F[线程访问自己的TLS副本]

实际应用场景[编辑 | 编辑源代码]

1. 错误码存储:如C库的errno通常实现为TLS变量 2. 线程特定缓存:避免锁竞争 3. 随机数生成器状态:保持每个线程的独立状态

高级主题:动态TLS[编辑 | 编辑源代码]

除了语言级别的TLS支持,操作系统还提供API级别的TLS:

  • Windows: TlsAlloc(), TlsGetValue(), TlsSetValue()
  • POSIX: pthread_key_create(), pthread_getspecific(), pthread_setspecific()

示例(POSIX):

#include <pthread.h>
#include <stdio.h>

pthread_key_t key;

void destructor(void *value) {
    free(value); // 清理线程特定数据
}

void *thread_func(void *arg) {
    int *data = malloc(sizeof(int));
    *data = pthread_self(); // 使用线程ID作为示例数据
    pthread_setspecific(key, data);
    printf("Thread %lu: data = %d\n", pthread_self(), *(int *)pthread_getspecific(key));
    return NULL;
}

int main() {
    pthread_key_create(&key, destructor);
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, thread_func, NULL);
    pthread_create(&t2, NULL, thread_func, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    pthread_key_delete(key);
    return 0;
}

性能考量[编辑 | 编辑源代码]

TLS访问通常比普通变量访问慢,因为:

  • 需要通过额外的内存引用(如通过线程环境块)
  • 某些架构需要特殊的指令支持

性能模型可以表示为: Taccess=Tbase+Ttls_overhead

限制与注意事项[编辑 | 编辑源代码]

1. TLS变量不能是自动存储期(不能声明在函数内非static) 2. 动态库中的TLS可能有平台特定的行为 3. 大量使用TLS可能增加线程创建开销 4. 某些嵌入式平台可能不支持TLS

总结[编辑 | 编辑源代码]

线程局部存储是多线程编程中的重要工具,它:

  • 提供线程隔离的数据存储
  • 避免不必要的同步开销
  • 简化线程安全代码的编写
  • 有语言级别和API级别两种实现方式

正确使用TLS可以显著提高多线程程序的可靠性和性能。