线程局部存储(Thread-Local Storage, TLS)
目录
一、线程局部存储(Thread-Local Storage, TLS)的演示使用代码
二、代码分析
1、线程局部存储(__thread关键字)
2、多线程创建与管理
(1) 线程创建
(2) 线程等待
3、线程函数(routine1 和 routine2)
(1) routine1:修改线程局部变量
(2) routine2:读取线程局部变量
4、地址打印函数(Addr)
5、代码执行结果分析
6、线程局部存储的局限性
7、潜在问题与改进
8、总结
一、线程局部存储(Thread-Local Storage, TLS)的演示使用代码
#include <pthread.h>
#include <iostream>
#include <string>
#include <unistd.h>// 该count叫做线程的局部存储!
__thread int count = 1;
// 线程局部存储有什么用?全局变量,我又不想让这个全局变量被其他线程看到!
// 线程局部存储,只能存储内置类型和部分指针std::string Addr(int &c)
{char addr[64];snprintf(addr, sizeof(addr), "%p", &c);return addr;
}void *routine1(void *args)
{(void)args;while (true){std::cout << "thread - 1, count = " << count << "[我来修改count], "<< "&count: " << Addr(count) << std::endl;count++;sleep(1);}
}void *routine2(void *args)
{(void)args;while (true){std::cout << "thread - 2, count = " << count<< ", &count: " << Addr(count) << std::endl;sleep(1);}
}int main()
{pthread_t tid1, tid2;pthread_create(&tid1, nullptr, routine1, nullptr);pthread_create(&tid2, nullptr, routine2, nullptr);pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);return 0;
}
二、代码分析
这段代码演示了线程局部存储(Thread-Local Storage, TLS)的使用,以及多线程环境下变量的访问行为。下面我将详细讲解其中的知识点:
1、线程局部存储(__thread关键字)
__thread int count = 1;
作用:__thread 是 GCC 提供的扩展关键字,用于声明线程局部存储变量。每个线程会拥有该变量的独立副本,线程间不会相互干扰。
特点:
-
每个线程访问的是自己的副本,修改不会影响其他线程。
-
适用于全局变量但需要线程隔离的场景。
-
只能用于内置类型和部分指针(复杂对象如
std::string不支持)。
对比普通全局变量:
-
普通全局变量会被所有线程共享,多线程修改会导致竞争条件(需用互斥锁保护)。
-
线程局部存储变量无需锁,因为天然隔离。
2、多线程创建与管理
(1) 线程创建
pthread_t tid1, tid2;
pthread_create(&tid1, nullptr, routine1, nullptr);
pthread_create(&tid2, nullptr, routine2, nullptr);
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
参数说明:
-
thread:输出参数,保存线程 ID。 -
attr:线程属性(如栈大小、调度策略),nullptr表示默认属性。 -
start_routine:线程入口函数。 -
arg:传递给入口函数的参数(此处未使用,强制转换为(void)args避免编译器警告)。
(2) 线程等待
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
作用:主线程等待子线程结束,避免主线程退出导致子线程被强制终止。
参数:
-
第一个参数:线程 ID。
-
第二个参数:用于获取线程返回值(此处未使用)。
3、线程函数(routine1 和 routine2)
(1) routine1:修改线程局部变量
void *routine1(void *args) {while (true) {std::cout << "thread - 1, count = " << count << "[我来修改count], "<< "&count: " << Addr(count) << std::endl;count++; // 修改的是线程1的副本sleep(1);}
}
行为:
-
每秒输出当前线程的
count值及其地址。 -
修改
count(仅影响线程1的副本)。
(2) routine2:读取线程局部变量
void *routine2(void *args) {while (true) {std::cout << "thread - 2, count = " << count<< ", &count: " << Addr(count) << std::endl;sleep(1);}
}
行为:
-
每秒输出当前线程的
count值及其地址。 -
不修改
count,但即使修改也不会影响线程1的副本。
4、地址打印函数(Addr)
std::string Addr(int &c) {char addr[64];snprintf(addr, sizeof(addr), "%p", &c);return addr;
}
作用:将变量的地址格式化为字符串,用于验证不同线程的 count 是否为同一内存地址。
关键点:
-
线程局部存储的变量在不同线程中地址不同(因为每个线程有独立副本)。
-
如果是普通全局变量,所有线程打印的地址会相同。
5、代码执行结果分析
预期输出:
-
两个线程输出的
count值初始均为1,但后续互不影响。 -
线程1的
count会递增(如1, 2, 3,...),而线程2的count始终为1(除非线程2也修改自己的副本)。 -
两个线程打印的
&count地址不同,证明是独立副本。
示例片段:

6、线程局部存储的局限性
-
仅支持简单类型:如
int、float、指针等,不支持std::string等复杂对象(会引发编译错误)。 -
初始化时机:变量在首次访问时初始化,线程退出时销毁。
-
替代方案:C++11 提供了更标准的
thread_local关键字:thread_local int count = 1; // 功能与 __thread 类似
7、潜在问题与改进
-
无限循环:线程函数没有退出条件,实际使用时需通过标志位控制。
-
输出竞争:
std::cout是共享资源,多线程同时输出可能导致交错(此处因sleep(1)延迟较难重现,但严格来说应加锁)。 -
C++11 线程库:建议使用
<thread>替代 POSIX 线程(更现代、类型安全):#include <thread> std::thread t1(routine1, nullptr); std::thread t2(routine2, nullptr); t1.join(); t2.join();
8、总结
-
核心知识点:线程局部存储(
__thread/thread_local)实现了变量的线程隔离。 -
关键观察:通过打印地址验证不同线程的变量副本独立性。
-
应用场景:需要全局变量但又避免多线程竞争时(如线程独立的计数器、临时缓冲区等)。
