C 语言线程局部存储 TLS
更新时间:2026-08-26。本文是
languages/c/主题专家层文档。全局变量全线程共享,但有时你希望"每个线程一份私有拷贝"——比如每个线程自己的错误码、自己的随机数种子。这就是线程局部存储(TLS)。本文用实测讲清__thread的用法和底层机制。
本文要回答的问题
__thread变量和普通全局变量有什么不同?- TLS 在底层是怎么实现的?每个线程怎么"找到"自己的那份?
- 什么时候该用 TLS?它有什么限制?
一、__thread:每个线程一份
c
__thread int tls_var = 0; // 线程局部变量
void *worker(void *arg) {
int id = *(int *)arg;
tls_var = id * 100; // 每个线程设自己的值
printf("线程 %d: tls_var=%d\n", id, tls_var);
}实测(3 个线程各自赋值):
text
主线程: tls_var=-1 ← 主线程自己的副本
线程 1: tls_var=100 ← 线程 1 的副本
线程 2: tls_var=200 ← 线程 2 的副本
主线程(最终): tls_var=-1 ← 主线程副本不受影响每个线程有自己独立的 tls_var 副本,互不干扰——这就是 TLS 的本质。
二、TLS 的底层实现
TLS 在 x86-64 上靠 FS 段寄存器实现:

- 每个线程有自己的 TLS 块(一块独立内存)
- 访问 TLS 变量 =
FS:偏移(FS 段寄存器指向当前线程的 TLS 块) - 线程切换时,FS 段寄存器跟着切换,天然"线程隔离"
errno 就是最经典的 TLS 实例——每个线程有独立的 errno,多线程下互不干扰(见 系统调用封装)。
三、两种 TLS 方式对比
| 方式 | 语法 | 特点 |
|---|---|---|
__thread 关键字 | __thread int x; | 静态 TLS,简单,但动态库里有限制 |
pthread_key | pthread_key_create 等 | 动态 TLS,灵活,可带析构函数 |
c
// pthread_key 方式(动态 TLS)
pthread_key_t key;
pthread_key_create(&key, destructor); // destructor 在线程退出时调用
pthread_setspecific(key, value); // 存
void *v = pthread_getspecific(key); // 取pthread_key 比 __thread 更灵活(支持动态创建、线程退出时的析构回调),但用起来繁琐。
四、TLS 的使用场景
| 场景 | 例子 |
|---|---|
| 每线程错误码 | errno(TLS 的经典实现) |
| 每线程随机种子 | 避免多线程下 rand 竞争 |
| 每线程缓冲区 | 避免加锁的临时缓冲区 |
| 每线程日志上下文 | 请求 ID、trace ID |
| 每线程内存池 | 无锁的内存分配 |
五、TLS 的限制
| 限制 | 说明 |
|---|---|
| 不能用于 C++ 的构造/析构 | __thread 只支持 POD 类型(C 里都是 POD) |
动态库里 __thread 受限 | 动态加载的 .so 里 __thread 可能不支持(用 pthread_key) |
| 访问稍慢 | 比普通全局变量多一次 FS 段寻址 |
| 不能是函数内 static | 函数内 static __thread 组合有语义陷阱 |
六、与本站主线衔接
- 线程机制:TLS 与线程控制块 TCB 的关系,见 线程创建。
- errno:errno 的 TLS 实现,见 系统调用封装。
- 无锁编程:TLS 是避免锁竞争的手段之一,见 内存屏障与原子。
- 信号:TLS 与信号处理器的交互,见 信号处理。
一句话总结
__thread 让每个线程有变量的私有副本(靠 FS 段寄存器寻址),errno 就是 TLS 的实例;它用"每线程一份"换掉"加锁共享",是无锁编程的利器,但动态库场景要改用 pthread_key。