面试必备之深入理解thread local
坚持思考,就会很酷。
一、面试回顾
时间:2025年 4月28岗位:数据库开发工程师公司:vmare/阿里云/kangao数据库形式:线下面试:自己感觉回答很好,但是估计过不了
一面
基础面试:
- 谈谈你对自旋锁理解
- 进程通信那个方式
- 线程局部存储
- read,write io过程。
项目面试:
- 干了这么多年, 你角色是什么,一个开发吗?
- 假如入你独立开发c特性,能不能做?
- 熟悉英语吗?开源社区 参与中文的还是英文的,英文资料行不行
- 你学校主任是谁,
- 在之前公司干什么事情?说的项目,结果 判断不是核心人员。(太武断了)
二、谈谈你对ThreadLocal理解
2.1 青铜被虐(工作0-5年):
思考:
- 听说过没接触过,不知道 怎么实现,然后陷入慌乱,之前线程,进程。
- 根本想不起来基础知识 局部变量、全局变量、堆、堆栈、静态变量区别 和这个有关系
划重点:
- c++ 不会凭空造一个新概念,都是基于原有基础上发展的
- c++ 特性都是依赖编译器,gcc,甚至操作系统。
2.2 王者归来(5-10年)
一、这个技术出现的背景、初衷和要达到什么样的目标或是要解决什么样的问题
在Linux系统中使用C/C++进行多线程编程时
- 对于局部变量来说,其不存在线程安全问题
- 全局变量来说 各个线程都可以访问的共享变量,因此它们存在多线程读写问题,采用原子或者锁来解决。
- 如果不采用锁,依然满足在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量,这就需要新的机制来实现,需要线程局部存储(TLS: Thread-Local Storage)
生命周期 | 函数作用域 | 手动管理 | 线程生命周期 |
访问效率 | 高(直接栈寻址) | 低(需指针间接访问) | 高(寄存器或 TLS 直接寻址) |
线程安全 | 天然隔离 | 需同步机制 | 天然隔离 |
适用场景 | 短期局部数据 | 动态分配的长周期数据 | 线程私有状态(跨函数共享) |
- 在 C++11 之前,开发者需依赖平台特定的 API(如 Linux 的
pthread_key_create
)实现线程局部存储 - 在c++11中,thread_local用于声明变量具有线程局部存储期。这意味着每个线程都拥有此变量的一个独立实例,每个线程只能访问自己的实例,不同线程间的实例互不影响
二、 这个技术适用的场景。任何技术都有其适用的场景
3FS项目中的thread_local使用展示了几个关键模式:
- 用于高效的线程局部缓存以避免重复计算
- 消除线程间的锁竞争,提高并发性能
- 优化内存分配模式,特别是在对象池和工作队列中
- 提供线程安全的随机数生成和状态跟踪
三、技术的组成部分和关键点
存储结构 | 编译器通过
寄存器直接访问TLS,无哈希表开销 | 基于线程内部的
哈希表存储 |
内存管理 | 线程结束时自动析构(RAII机制) | 需手动调用
,否则可能内存泄漏 |
性能 | 接近直接内存访问(1~3周期) | 哈希表查询(约10~100周期) |
类成员支持 | 必须为
(如
) | 非
,通过实例关联 |
四、技术的底层原理和关键实现
https://github.com/watchpoints/master-cpp/blob/main/cpp/chapt-3-thread-local/therad1.cpp /* * @Date: 2025-05-13 21:09:48 * @Author: 后端开发成长指南 watchpoints * @FilePath: 知识地图--高频面试题 * @Git: https://github.com/watchpoints/master-cpp #include <iostream> #include <thread> #include <memory> //用CPU的视角,解读 thread local 的工作原理 //g++ -S -O2 thread1.cpp -o thread.s //https://godbolt.org/z/o6njsaan9 thread_local int x = 1; // 线程局部变量(全局作用域) void thread_func() { std::cout << "Thread " << std::this_thread::get_id() << ": x address = " << &x << std::endl; } int func() { thread_local int b = 2; // 线程局部变量(函数局部作用域) return b; // mov eax, DWORD PTR fs:func()::b@tpoff } int main() { std::thread t1(thread_func); // 创建线程1 std::thread t2(thread_func); // 创建线程2 //同一个变量为什么不同线程,看到不同地址? //栈也实现,为什么不栈代替 // Thread 140289368270400: x address = 0x7f97a9f6263c // Thread 140289359877696: x address = 0x7f97a976163c t1.join(); t2.join(); return 0; }
在线程创建时,运行时(glibc + dynamic linker)会为每个线程分配两个独立区域:
- TLS 块(Thread-Local Storage area):包含所有静态和动态 TLS 对象的私有副本,以及线程控制块(TCB)。
- 线程栈(Thread Stack):独立的堆栈区域,用于函数调用、局部变量等。
这两个区域通常是分开 mmap/分配的,且在 x86-64 上通过段寄存器(FS 或 GS)来访问 TLS,而通过 RSP/RBP 访问栈
TLS 块 | FS(或 GS)段基址 + 偏移 |
|
线程栈 | RSP/RBP 寄存器 | 通过常规栈寻址(
、
|
Local‑Exec TLS 访问模型
编译/链接阶段
- 编译器将所有静态已初始化的 TLS 对象收集到 ELF 的
.tdata
节,并由链接器在最终的可执行文件或共享库的 PT_TLS 段内生成一个“TLS 模板”。 - 每个 TLS 符号都会被赋予一个固定的节内偏移
st_value
,对应重定位类型@tpoff
。
运行阶段
- 线程创建glibc/动态链接器在 pthread_create 时,为新线程 mmap 出一块内存,复制主 TLS 模板(.tdata+.tbss)并在尾部附加 TCB 结构。通过 arch_prctl(ARCH_SET_FS, tcb_address) 将 fs 段基址设置到刚分配的 TCB 处。
- 直接访问之后的 mov eax, DWORD PTR fs:offset 便能在单条指令内读出该线程的私有变量,速度媲美普通全局变量读取。
五、对比java怎么实现的?
--------------------——END--------------------------
我是谁
刚刚好,是最难得的美好
我就在这里 ,我刚刚好。
我正在做的事情是
1. 目标:拿百万年薪
- 想进入一线大厂,但在C++学习和应用上存在瓶颈,渴望跨越最后一道坎。
2. 现状:缺乏实战,渴望提升动手能力
- 公司的项目不会重构,没有重新设计的机会,导致难以深入理解需求。
- 想通过阅读优秀的源码,提高代码能力,从"不会写"到"敢写",提升C++编程自信。
- 需要掌握高效学习和实践的方法,弥补缺乏实战经验的短板。
3. 价值:成为优秀完成任务,成为团队、公司都认可的核心骨干。
优秀地完成任务= 高效能 + 高质量 + 可持续 + 可度量错误示范:
- 不少同学工作很忙,天天加班,做了很多公司的事情。 但是 不是本团队事情,不是本部门事情,领导不认可,绩效不高
- 做低优先级的任务,绩效不高,随时被优化
如果您觉得阅读本文对您有帮助,请点一下“点赞,转发” 按钮,您的“点赞,转发” 将是我最大的写作动力!
参考:第一手资料
- ThreadLocal是Java中实现线程本地存储(TLS)的核心类,通过为每个线程维护独立的变量副本,解决多线程环境下的数据竞争问题
- https://www.bilibili.com/video/BV1Lo4y1g787/
- https://en.cppreference.com/w/cpp/language/storage_duration
- https://www.cnblogs.com/curiositywang/p/18231696
- Facebook的folly库中的ThreadLocal实现
- https://github.com/lefticus/cpp_weekly
- https://www.youtube.com/watch?v=q9_vljSaBDg
- https://www.youtube.com/watch?v=vlskUyjBSQA
- https://github.com/nilaoda/BBDown 【done】