线程局部存储
线程局部存储 (Thread-Specific Storage, TSS) 让每个线程持有自己的私有数据副本。它适合存放“线程上下文”这类不应在线程间共享的对象。
本节接口来自 <threads.h>,因此也受实现支持度影响;不支持时需要在平台层提供等价封装。
1. 核心接口
tss_create 创建键,tss_set 绑定当前线程的数据指针,tss_get 读取当前线程绑定值,tss_delete 删除键。创建键时可提供析构函数,在线程退出时自动清理该线程绑定的数据。
c
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>
static tss_t key;
void cleanup(void *ptr) {
free(ptr);
}
int worker(void *arg) {
int id = *(int *)arg;
int *slot = malloc(sizeof *slot);
if (slot == NULL) {
return 1;
}
*slot = id * 10;
tss_set(key, slot);
printf("thread %d -> %d\n", id, *(int *)tss_get(key));
return 0;
}
int main(void) {
thrd_t t1, t2;
int id1 = 1, id2 = 2;
if (tss_create(&key, cleanup) != thrd_success) {
return 1;
}
thrd_create(&t1, worker, &id1);
thrd_create(&t2, worker, &id2);
thrd_join(t1, NULL);
thrd_join(t2, NULL);
tss_delete(key);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
可能的输出(示例):
bash
<输出与输入或平台相关,请以实际运行为准>
2. 适用场景
日志上下文、错误码缓冲、解析器状态等都可放入线程局部存储。它能避免频繁把上下文指针层层透传,也能减少不必要的共享同步。
3. 实践建议
线程局部存储适合“小而稳定”的线程私有状态,不适合承载大规模生命周期复杂的数据结构。若对象体积较大或依赖关系复杂,显式上下文传递通常更清楚。
4. 生命周期配合要点
tss_delete 只删除键本身,不会主动遍历并释放所有线程上已绑定的数据;每个线程对象的回收主要依赖线程退出时触发的析构回调。因此,键的创建与销毁应和线程生命周期设计一起考虑,避免出现“键还在但线程已重建”或“键被删但线程仍在运行”的混乱状态。
5. 与 _Thread_local 的分工
语言级 _Thread_local 适合编译期即可确定的线程私有对象;TSS 更适合运行期按键管理、可动态装配的数据。前者访问成本低、语义直接,后者在库化封装和插件式组件中更灵活。根据对象是否需要动态注册与销毁来选择,通常更容易得到清晰设计。