《C++Linux编程进阶:从0实现muduo 》-第8讲.C++面试如何高效获取线程ID
章节重点
在C++面试时,经常被问到如果高效获取线程ID,但不少同学都不知道如何回答。
重点是通过__thread关键字。
重点内容

视频讲解:《C++Linux编程进阶:从0实现muduo C++网络框架系列》-第8讲. C++面试如何高效获取线程ID
测试获取线程ID的性能
单线程,测试3次,每次获取100万次线程ID。
| 第一次(秒) | 第二次(秒) | 第三次(秒) | 平均性能(秒) | |
| 使用__thread版本耗时 | 0.003284 | 0.003541 | 0.003646 | 0.00349 |
| 不使用__thread版本耗时 | 0.174818 | 0.148581 | 0.201204 | 0.174867 秒 |
| 性能差距(倍数) | 53.2 | 41.9 | 55.2 | 50.1 |
4个线程,测试3次,每次每个线程获取100万次线程ID。
| 第一次(秒) | 第二次(秒) | 第三次(秒) | 平均性能(秒) | |
| 使用__thread版本耗时 | 0.004119 | 0.004096 | 0.003457 | 0.003890 |
| 不使用__thread版本耗时 | 0.212637 | 0.214584 | 0.205432 | 0.210884 |
| 性能差距(倍数) | 51.6 | 52.4 | 59.4 | 54.47 |
代码改动
lesson8
-
base/CurrentThread.h/cc
-
examples/test_currentthread.cc
-
examples/test_thread_performance.cc

1 线程局部存储__thread关键字的作用
我来详细分析 CurrentThread 的设计原理和性能优化,并给出时序图。
1.1 设计原理分析
1.1.1 线程局部存储(thread local store, TLS)机制
__thread int t_cachedTid = 0;// 线程局部存储变量
// int g_t_cachedTid = 0;// 全局变量,多线程共享__thread char t_tidString[32];
__thread int t_tidStringLength = 6;
__thread const char* t_threadName = "unknown";
-
使用 __thread 关键字实现线程局部存储
-
每个线程都有自己独立的变量副本 和 int g_t_cachedTid = 0;// 全局变量,多线程共享 不一样
-
变量的生命周期与线程相同
-
避免了多线程访问时的同步开销,内存模型:
// 内存布局示意
Thread 1: [t_cachedTid = 1001]
Thread 2: [t_cachedTid = 1002]
Thread 3: [t_cachedTid = 1003]
-
每个线程有独立的内存区域
-
变量存储在线程的栈或TLS段中
-
线程间互不影响
1.1.2 懒加载模式
inline int tid()
{if (__builtin_expect(t_cachedTid == 0, 0)){cacheTid();}return t_cachedTid;
}
-
采用懒加载策略,只在第一次调用时获取线程ID
-
使用 __builtin_expect 优化分支预测,为什么能优化?这里实测和
inline int tid()
{if (t_cachedTid == 0){cacheTid();}return t_cachedTid;
}
没啥区别,所以不用理会这个优化。
-
后续调用直接返回缓存值,避免系统调用
1.2 性能优化分析
1.2.1 减少系统调用
-
系统调用(syscall(SYS_gettid))是相对昂贵的操作
-
通过缓存机制,将系统调用次数从每次获取都调用降低到每个线程只调用一次
-
在多线程环境下,性能提升更加明显
1.2.2 字符串预格式化
void cacheTid()
{if (t_cachedTid == 0){t_cachedTid = gettid();t_tidStringLength = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid);}
}
-
预先计算并缓存线程ID的字符串表示
-
避免重复的整数到字符串转换操作
-
缓存字符串长度,避免重复计算
1.3 CurrentThread::tid() 函数时序图

1.4 使用场景
这种设计特别适合:
1.日志系统
-
频繁获取线程ID用于日志记录
-
需要线程标识进行问题追踪
2.高并发服务器
-
需要频繁获取线程ID的场景
-
对性能要求较高的系统
1.5 是否可以不用__thread
只使用 extern int t_cachedTid 而不使用 __thread 的风险分析。
1.5.1 数据竞争问题
如果不使用__thread关键字,那t_cachedTid变成所有线程共享了。
// 不使用__thread的情况
extern int t_cachedTid; // 全局变量,所有线程共享void cacheTid()
{if (t_cachedTid == 0){t_cachedTid = gettid(); // 危险!多线程同时访问}
}
-
多个线程同时执行 cacheTid()
-
可能导致线程A写入的ID被线程B覆盖
-
最终可能所有线程都得到错误的线程ID
1.5.2 实际案例分析
假设有两个线程同时执行:
// 线程A
if (t_cachedTid == 0) // 检查为0
t_cachedTid = gettid(); // 假设得到ID=100
// 但此时线程B可能已经覆盖了这个值// 线程B
if (t_cachedTid == 0) // 也检查为0
t_cachedTid = gettid(); // 假设得到ID=200
// 覆盖了线程A的值
1.5.3 正确的做法
// 使用__thread的情况
__thread int t_cachedTid = 0; // 每个线程独立存储void cacheTid()
{if (t_cachedTid == 0){t_cachedTid = gettid(); // 安全!每个线程有自己的存储空间}
}
1.5.4 小结
在现代多线程程序中,应该始终使用 __thread 来保证线程局部存储的正确性和性能。这是C++中处理线程局部数据的最佳实践。
2 使用和不使用__thread缓存线程id性能差异测试
2.1 测试代码
测试范例:examples/test_thread_performance.cc
#include "base/CurrentThread.h"
#include "base/Timestamp.h"
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <unistd.h> // syscall()
#include <sys/syscall.h> // SYS_gettidusing namespace mymuduo;// 不使用__thread的版本
class NonThreadLocal {
public:static int getTid() {return static_cast<int>(::syscall(SYS_gettid));}
};// 测试函数
void testPerformance(int iterations) {std::cout << "开始性能测试,迭代次数: " << iterations << std::endl;// 测试使用__thread的版本{Timestamp start = Timestamp::now();for (int i = 0; i < iterations; ++i) {CurrentThread::tid();}Timestamp end = Timestamp::now();double time = timeDifference(end, start);std::cout << "使用__thread版本耗时: " << time << " 秒" << std::endl;}// 测试不使用__thread的版本{Timestamp start = Timestamp::now();for (int i = 0; i < iterations; ++i) {NonThreadLocal::getTid();}Timestamp end = Timestamp::now();double time = timeDifference(end, start);std::cout << "不使用__thread版本耗时: " << time << " 秒" << std::endl;}
}// 多线程测试
void multiThreadTest(int threadCount, int iterations) {std::cout << "\n开始多线程测试,线程数: " << threadCount << ", 每个线程迭代次数: " << iterations << std::endl;std::vector<std::thread> threads;// 测试使用__thread的版本{Timestamp start = Timestamp::now();for (int i = 0; i < threadCount; ++i) {threads.emplace_back([iterations]() {for (int j = 0; j < iterations; ++j) {CurrentThread::tid();}});}for (auto& thread : threads) {thread.join();}Timestamp end = Timestamp::now();double time = timeDifference(end, start);std::cout << "多线程使用__thread版本总耗时: " << time << " 秒" << std::endl;}threads.clear();// 测试不使用__thread的版本{Timestamp start = Timestamp::now();for (int i = 0; i < threadCount; ++i) {threads.emplace_back([iterations]() {for (int j = 0; j < iterations; ++j) {NonThreadLocal::getTid();}});}for (auto& thread : threads) {thread.join();}Timestamp end = Timestamp::now();double time = timeDifference(end, start);std::cout << "多线程不使用__thread版本总耗时: " << time << " 秒\n" << std::endl;}
}int main() {// 单线程测试std::cout << "=== 单线程测试 ===" << std::endl;testPerformance(1000000); // 100万次迭代// 多线程测试std::cout << "\n=== 多线程测试 ===" << std::endl;multiThreadTest(4, 1000000); // 4个线程,每个线程100万次迭代return 0;
}
性能测试程序,它包含以下测试场景:
1.单线程测试:
-
对比使用和不使用 __thread 时获取线程ID的性能
-
每个版本执行100万次迭代
-
使用 Timestamp 类精确测量执行时间
2.多线程测试:
-
创建4个线程同时运行
-
每个线程执行100万次迭代
-
对比多线程环境下两种实现的性能差异
要编译和运行这个测试,你需要执行以下命令:
cd build
cmake ..
make
./bin/test_thread_performance
2.2 测试结果
单线程,测试3次,每次获取100万次线程ID。
| 第一次(秒) | 第二次(秒) | 第三次(秒) | 平均性能(秒) | |
| 使用__thread版本耗时 | 0.003284 | 0.003541 | 0.003646 | 0.010471 |
| 不使用__thread版本耗时 | 0.174818 | 0.148581 | 0.201204 | 0.174867 |
| 性能差距(倍数) | 53.2 | 41.9 | 55.2 | 50.1 |
4个线程,测试3次,每次每个线程获取100万次线程ID。
| 第一次(秒) | 第二次(秒) | 第三次(秒) | 平均性能(秒) | |
| 使用__thread版本耗时 | 0.004119 | 0.004096 | 0.003457 | 0.003890 |
| 不使用__thread版本耗时 | 0.212637 | 0.214584 | 0.205432 | 0.210884 |
| 性能差距(倍数) | 51.6 | 52.4 | 59.4 | 54.47 |
3 章节总结
面试时被问到项目优化,可以讲使用__thread关键字缓存各自线程的ID,这样日志需要获取线程ID时有更好的性能,通过测试对比性能有50倍左右的差距。
具体实现过程大家可以看 1.3 CurrentThread::tid() 函数时序图 章节。
相关文章:
《C++Linux编程进阶:从0实现muduo 》-第8讲.C++面试如何高效获取线程ID
章节重点 在C面试时,经常被问到如果高效获取线程ID,但不少同学都不知道如何回答。 重点是通过__thread关键字。 重点内容 视频讲解:《CLinux编程进阶:从0实现muduo C网络框架系列》-第8讲. C面试如何高效获取线程ID 测试获取线…...
【Tauri2】011——菜单menu(2)
前言 前面简单地创建了菜单,接下来就来试试菜单中的action Rust中菜单项注册action AppHandle in tauri - Rusthttps://docs.rs/tauri/2.4.0/tauri/struct.AppHandle.html#method.on_menu_event这就需要用到App或者AppHandle中的方法on_menu_event #[must_use] …...
架构设计基础系列:面向对象设计的原则
引言 面向对象设计(Object-Oriented Design,OOD)是软件开发中的重要概念,其核心在于通过对象、类、继承、封装和多态等机制,实现对现实世界问题的抽象和建模。OOD不仅有助于提高代码的可重用性、可维护性和可扩展性&a…...
UE5学习笔记 FPS游戏制作35 使用.csv配置文件
文章目录 导入.csv要求首先创建一个结构体导入配置文件读取配置 导入 .csv要求 第一行必须包含标题 第一列的内容必须不能重复,因为第一列会被当成行的名字,在数据处理中发挥类似于字典的key的作用 当前的配置文件内容如下 首先创建一个结构体 结构…...
嵌入式单片机ADC数模转换的基本方法
第一:模数转换的概述 1:模数转换的概念 一般在电路中,信号分为两种,一种是模拟信号,一种是数字信号,绝大多数传感器采集的都是模拟信号,如温度、湿度、烟雾浓度、亮度.......,但是对于计算机需要处理的数字信号,那就需要利用电路把模拟信号转换为数字信号,这个转换的…...
Web数据挖掘及其在电子商务中的研究与应用
标题:Web数据挖掘及其在电子商务中的研究与应用 内容:1.摘要 随着互联网的飞速发展,Web数据呈现出爆炸式增长,电子商务领域更是积累了海量数据。在此背景下,对Web数据进行有效挖掘并应用于电子商务具有重要意义。本研究旨在探索Web数据挖掘技…...
01-Docker 安装
1、安装环境介绍 安装环境:Linux CentOS 7 本安装教程参考Docker官方文档,地址如下:https://docs.docker.com/engine/install/centos/ 2、卸载旧版docker 首先如果系统中已经存在旧的Docker,则先卸载: yum remove do…...
Redis 的缓存雪崩、击穿、穿透及其解决办法
文章目录 Redis 的缓存雪崩、击穿、穿透及其解决办法缓存雪崩解决办法 缓存击穿解决方案 缓存穿透解决方案 Redis 的缓存雪崩、击穿、穿透及其解决办法 本篇文章回顾 Redis 当中缓存崩溃、击穿、穿透现象以及相应的解决办法,主要的参考资料是:https://w…...
使用 Selenium 构建简单高效的网页爬虫
在当今数据驱动的世界中,网络爬虫已成为获取网络信息的重要工具。本文将介绍如何使用 Python 和 Selenium 构建一个简单而高效的网页爬虫,该爬虫能够处理现代网站的动态内容,支持代理设置和用户配置文件。 为什么选择 Selenium? …...
性能比拼: Pingora vs Nginx (My NEW Favorite Proxy)
本内容是对知名性能评测博主 Anton Putra Pingora vs Nginx Performance Benchmark: My NEW Favorite Proxy! 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 介绍 在本视频中,我们将对比 Nginx 和 Pingora(一个用于构建网络服务的 Rust 框架…...
Ranger一分钟
简介 Ranger Admin:Web UIPolicy Admin Tool:定义和管理策略的模块Ranger Plugins:HDFS、Hive、HBase、Kafka、Storm、YARNRanger UserSync: LDAP、Active DirectoryRanger KMS:管理和保护数据加密的密钥 加密密钥管理…...
STM32单片机入门学习——第5节: [3-1]GPIO输出
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.04.01 STM32开发板学习——第5节: [3-1]GPIO输出 前言开发板说明引用解答和…...
Open GL ES ->模型矩阵、视图矩阵、投影矩阵等变换矩阵数学推导以及方法接口说明
Open GL ES 变换矩阵详解 一、坐标空间变换流程 局部空间 ->Model Matrix(模型矩阵)-> 世界空间 世界空间->View Matrix(视图矩阵)->观察空间 观察空间 ->Projection Matrix(投影矩阵)->裁剪空间 裁剪空间 ->ViewPort Transform(视口变换)>屏幕空间 …...
AI提示词:自然景区智能客服
提示描述 专为自然景区游客设计的智能客服系统,旨在通过人工智能技术提供实时、准确的景区信息、游览建议、安全提示和服务支持,提升游客的体验质量和满意度。 提示词 # Role: 自然景区智能客服## Profile: - Author: xxx - Version: 1.0 - Language: …...
c#的反射和特性
在 C# 中,反射(Reflection)和特性(Attributes)是两个强大的功能,它们在运行时提供元编程能力,广泛用于框架开发、对象映射和动态行为扩展。以下是对它们的详细介绍,包括定义、用法、…...
智能体项目实现AI对话流式返回效果
1、智能体项目里与AI大模型对话的时候,需要从后端的流式接口里取数据并实现打字机渲染效果。这里涉及到 Markdown 格式的渲染,所以需要配合 marked.js 实现,安装 marked.js : npm install marked 引用: import { ma…...
定时任务(python)
介绍 🧩 什么是“定时任务”? 定时任务,就是按照设定的时间间隔或时间点自动执行某些操作。比如: • 每天早上8点发通知 • 每隔10秒采集一次数据 • 每小时清理一次缓存相关使用 ✅ 最简单的方式:while True tim…...
Python学习第二十七天
yield关键字 yield关键字扮演着核心角色,主要用于处理异步数据流和请求调度。 主要作用 生成器函数:将方法转换为生成器,可以逐步产生结果而不需要一次性返回所有数据 异步处理:支持Scrapy的异步架构,提高爬取效率 …...
Docker Compose 启动jar包项目
参考文章安装Docker和Docker Compose 点击跳转 配置 创建一个文件夹存放项目例如mydata mkdir /mydata上传jar包 假设我的jar包名称为goudan.jar 编写dockerfile文件 vim app-dockerfile按键盘上的i进行编辑 # 使用jdk8 FROM openjdk:8-jre# 设置时区 上海 ENV TZAsia/Sh…...
pytorch中dataloader自定义数据集
前言 在深度学习中我们需要使用自己的数据集做训练,因此需要将自定义的数据和标签加载到pytorch里面的dataloader里,也就是自实现一个dataloader。 数据集处理 以花卉识别项目为例,我们分别做出图片的训练集和测试集,训练集的标…...
SQL Server:触发器
在 SQL Server Management Studio (SSMS) 中查看数据库触发器的方法如下: 方法一:通过对象资源管理器 连接到 SQL Server 打开 SSMS,连接到目标数据库所在的服务器。 定位到数据库 在左侧的 对象资源管理器 中,展开目标数据库&a…...
标题:利用 Rork 打造定制旅游计划应用程序:一步到位的指南
引言: 在数字化时代,旅游计划应用程序已经成为旅行者不可或缺的工具。但开发一个定制的旅游应用可能需要耗费大量时间与精力。好消息是,Rork 提供了一种快捷且智能的解决方案,让你能轻松实现创意。以下是使用 Rork 创建一个定制旅…...
WebSocket原理详解(二)
WebSocket原理详解(一)-CSDN博客 目录 1.WebSocket协议的帧数据详解 1.1.帧结构 1.2.生成数据帧 2.WebSocket协议控制帧结构详解 2.1.关闭帧 2.2.ping帧 2.3.pong帧 3.WebSocket心跳机制 1.WebSocket协议的帧数据详解 1.1.帧结构 WebSocket客户端与服务器通信的最小单…...
计算声音信号波形的谐波
计算声音信号波形的谐波 1、效果 2、定义 在振动分析中,谐波通常指的是信号中频率是基频整数倍的成分。基频是振动的主要频率,而谐波可能由机械系统中的非线性因素引起。 3、流程 1. 信号生成:生成或加载振动信号数据(模拟或实际数据)。 2. 预处理:预处理数据,如去噪…...
RepoReporter 仿照`TortoiseSVN`项目监视器,能够同时支持SVN和Git仓库
RepoReporter 项目地址 RepoReporter 一个仓库监视器,仿照TortoiseSVN项目监视器,能够同时支持SVN和Git仓库。 工作和学习会用到很多的仓库,每天都要花费大量的时间在频繁切换文件夹来查看日志上。 Git 的 GUI 工具琳琅满目,Git…...
C++多线程的性能优化
高效线程池设计与工作窃取算法实现解析 1. 引言 现代多核处理器环境下,线程池技术是提高程序并发性能的重要手段。本文解析一个采用工作窃取(Work Stealing)算法的高效线程池实现,通过详细代码分析和性能测试展示其优势。 2. 线程池核心设计 2.1 类结…...
【TS学习】(19)TS中的类
在 TypeScript 中,类(Class) 是面向对象编程的核心结构,用于封装数据和行为。TypeScript 的类继承了 JavaScript 的类特性,并增加了类型系统和高级功能的支持(如访问修饰符、存取器和装饰器)。 …...
UI设计系统:如何构建一套高效的设计规范?
UI设计系统:如何构建一套高效的设计规范? 1. 色彩系统的建立与应用 色彩系统是设计系统的基础之一,它不仅影响界面的整体美感,还对用户体验有着深远的影响。首先,设计师需要定义主色调、辅助色和强调色,并…...
深度学习--softmax回归
回归可以用于预测多少的问题,预测房屋出售价格,棒球队可能获胜的的常数或者患者住院的天数。 事实上,我们也对分类问题感兴趣,不是问 多少,而是问哪一个 1 某个电子邮件是否属于垃圾邮件 2 某个用户可能注册还是不注册…...
【计算机网络】记录一次校园网无法上网的解决方法
问题现象 环境:实训室教室内时间:近期突然出现 (推测是学校在施工,部分设备可能出现问题)症状: 连接校园网 SWXY-WIFI 后: 连接速度极慢偶发无 IP 分配(DHCP 失败)即使分…...
