线程局部存储(Thread-Local Storage, TLS)

目录

[一、线程局部存储(Thread-Local Storage, TLS)的演示使用代码](#一、线程局部存储(Thread-Local Storage, TLS)的演示使用代码)

二、代码分析

1、线程局部存储(__thread关键字)

2、多线程创建与管理

[(1) 线程创建](#(1) 线程创建)

[(2) 线程等待](#(2) 线程等待)

[3、线程函数(routine1 和 routine2)](#3、线程函数(routine1 和 routine2))

[(1) routine1:修改线程局部变量](#(1) routine1:修改线程局部变量)

[(2) routine2:读取线程局部变量](#(2) routine2:读取线程局部变量)

4、地址打印函数(Addr)

5、代码执行结果分析

6、线程局部存储的局限性

7、潜在问题与改进

8、总结


一、线程局部存储(Thread-Local Storage, TLS)的演示使用代码

cpp 复制代码
#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关键字)

cpp 复制代码
__thread int count = 1;

作用__thread 是 GCC 提供的扩展关键字,用于声明线程局部存储变量。每个线程会拥有该变量的独立副本,线程间不会相互干扰。

特点

  • 每个线程访问的是自己的副本,修改不会影响其他线程。

  • 适用于全局变量但需要线程隔离的场景。

  • 只能用于内置类型部分指针 (复杂对象如 std::string 不支持)。

对比普通全局变量

  • 普通全局变量会被所有线程共享,多线程修改会导致竞争条件(需用互斥锁保护)。

  • 线程局部存储变量无需锁,因为天然隔离。

2、多线程创建与管理

(1) 线程创建

cpp 复制代码
pthread_t tid1, tid2;
pthread_create(&tid1, nullptr, routine1, nullptr);
pthread_create(&tid2, nullptr, routine2, nullptr);

函数原型

cpp 复制代码
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) 线程等待

cpp 复制代码
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);

作用:主线程等待子线程结束,避免主线程退出导致子线程被强制终止。

参数

  • 第一个参数:线程 ID。

  • 第二个参数:用于获取线程返回值(此处未使用)。

3、线程函数(routine1routine2

(1) routine1:修改线程局部变量

cpp 复制代码
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:读取线程局部变量

cpp 复制代码
void *routine2(void *args) {
    while (true) {
        std::cout << "thread - 2, count = " << count
                  << ", &count: " << Addr(count) << std::endl;
        sleep(1);
    }
}

行为

  • 每秒输出当前线程的 count 值及其地址。

  • 不修改 count,但即使修改也不会影响线程1的副本。

4、地址打印函数(Addr

cpp 复制代码
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、线程局部存储的局限性

  • 仅支持简单类型 :如 intfloat、指针等,不支持 std::string 等复杂对象(会引发编译错误)。

  • 初始化时机:变量在首次访问时初始化,线程退出时销毁。

  • 替代方案 :C++11 提供了更标准的 thread_local 关键字:

    cpp 复制代码
    thread_local int count = 1; // 功能与 __thread 类似

7、潜在问题与改进

  • 无限循环:线程函数没有退出条件,实际使用时需通过标志位控制。

  • 输出竞争std::cout 是共享资源,多线程同时输出可能导致交错(此处因 sleep(1) 延迟较难重现,但严格来说应加锁)。

  • C++11 线程库 :建议使用 <thread> 替代 POSIX 线程(更现代、类型安全):

    cpp 复制代码
    #include <thread>
    std::thread t1(routine1, nullptr);
    std::thread t2(routine2, nullptr);
    t1.join(); t2.join();

8、总结

  • 核心知识点 :线程局部存储(__thread/thread_local)实现了变量的线程隔离。

  • 关键观察:通过打印地址验证不同线程的变量副本独立性。

  • 应用场景:需要全局变量但又避免多线程竞争时(如线程独立的计数器、临时缓冲区等)。

相关推荐
秃了也弱了。9 分钟前
python实现定时任务:schedule库、APScheduler库
开发语言·python
weixin_4407305019 分钟前
java数组整理笔记
java·开发语言·笔记
Xの哲學34 分钟前
Linux SMP 实现机制深度剖析
linux·服务器·网络·算法·边缘计算
Thera77735 分钟前
状态机(State Machine)详解:原理、优缺点与 C++ 实战示例
开发语言·c++
2501_906150561 小时前
私有部署问卷系统操作实战记录-DWSurvey
java·运维·服务器·spring·开源
linux开发之路1 小时前
C++高性能日志库开发实践
c++·c++项目·后端开发·c++新特性·c++校招
知识分享小能手1 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04的Linux网络配置(14)
linux·学习·ubuntu
钦拆大仁1 小时前
单点登录SSO登录你了解多少
服务器·sso
皇族崛起1 小时前
【视觉多模态】- scannet 数据的 Ubuntu 百度网盘全速下载
linux·ubuntu·3d建模·dubbo
niucloud-admin1 小时前
java服务端——controller控制器
java·开发语言