1. 模块概述
1.1 CurrentThread
唯一核心目的:用最快的速度,获取当前线程的 ID。
1.2 Thread
线程封装类,提供跨平台的线程管理功能。Thread 的核心设计目标包括:
- 线程创建与管理:封装 std::thread 提供统一的线程接口
- 线程 ID 获取:提供获取真实线程 ID 的能力
- 线程命名:支持为线程设置名称便于调试
- 生命周期管理:自动管理线程的启动和销毁
| 功能 | std::thread | mymuduo::Thread |
|---|---|---|
| 启动线程 | ✅ | ✅ |
| 等待线程 | ✅ | ✅ |
| 获取线程 ID | ❌ (需要额外调用) | ✅ (自动缓存) |
| 设置线程名称 | ❌ (平台相关) | ✅ (统一接口) |
| 统计线程数量 | ❌ | ✅ (全局计数) |
| 析构安全 | ❌ (不调用 join/detach 会崩溃) | ✅ (自动处理) |
cpp
+--------------------------------------------------+
| Thread |
+--------------------------------------------------+
| |
| +-------------+ +-------------+ |
| | std::thread | | tid_ | |
| | (封装) | | (真实ID) | |
| +-------------+ +-------------+ |
| |
| +-------------+ +-------------+ |
| | name_ | | func_ | |
| | (线程名) | | (线程函数) | |
| +-------------+ +-------------+ |
| |
+--------------------------------------------------+
2. 源码
2.1 CurrentThread.h
cpp
#pragma once
#include <unistd.h> // 提供系统调用基础接口
#include <sys/syscall.h> // 包含系统调用号定义
// pthread获取的线程ID本质是进程内线程标识符(可能是指针或整数),仅在进程内唯一,不同进程的 pthread_t可能重复
namespace mymuduo{
namespace CurrentThread {
// 线程局部变量声明,每个线程独立拥有该变量副本
extern thread_local int t_cachedTid;
// 缓存线程ID的函数声明
void cacheTid();
// __builtin_expect内置函数获取线程ID,通过__builtin_expect优化分支预测
// 优化的是机器码指令顺序而不是程序逻辑
// 优化前:
// 1. 检查t_cachedTid是否为0(冷路径)
// 2. 如果为0,走路径1调用cacheTid()缓存线程ID
// 3. 如果不为0,跳转至路径2返回缓存的线程ID
// 优化后:
// 1. 检查t_cachedTid是否为0(冷路径)
// 2. 如果为0,跳转至路径1调用cacheTid()缓存线程ID
// 3. 如果不为0,走路径2返回缓存的线程ID
inline int tid() {
// 预期t_cachedTid非0的情况更常见(分支预测优化)
if (__builtin_expect(t_cachedTid == 0, 0)) {
cacheTid(); // 首次调用时执行系统调用获取ID并缓存,路径1:不常见路径(冷路径)
}
return t_cachedTid; // 返回缓存的线程ID,径2:常见路径(热路径)
}
}
}
2.2 CurrentThread.cpp
cpp
#include "CurrentThread.h"
namespace mymuduo{
namespace CurrentThread {
// 线程局部变量定义,初始化为0(未缓存状态)
thread_local int t_cachedTid = 0;
// 缓存线程ID的实现函数
void cacheTid() {
if (t_cachedTid == 0) {
// 通过SYS_gettid系统调用获取线程ID(Linux特有的系统调用)
t_cachedTid = static_cast<pid_t>(::syscall(SYS_gettid));
}
}
}
}
// 测试代码(编译时默认不启用)
#if 0
#include <iostream>
using namespace mymuduo::CurrentThread;
int main() {
std::cout << "Current thread ID: " << tid() << std::endl;
return 0;
}
#endif
// 测试成功
2.3 Thread.h
cpp
#pragma once
#include "NonCopyable.h"
#include <functional>
#include <thread>
#include <memory>
#include <unistd.h>
#include <string>
#include <atomic>
namespace mymuduo
{
class Thread : NonCopyable
{
public:
using ThreadFunc = std::function<void()>; // 定义线程执行的函数类型,使用std::function包装可调用对象
// 构造函数,接收线程执行的函数和线程名称(默认为空字符串)
explicit Thread(ThreadFunc, const std::string &name = std::string());
// 析构函数
~Thread();
void start(); // 启动线程的方法
void join(); // 等待线程结束的方法
bool started() { return started_; } // 获取线程是否已启动的状态
pid_t tid() const { return tid_; } // 获取线程的ID
const std::string &name() const { return name_; } // 获取线程的名称
static int numCreated() { return numCreated_; } // 获取已创建的线程数量
private:
void setDefaultName(); // 设置默认线程名称
bool started_; // 线程是否已启动的标志
bool joined_; // 线程是否已join的标志
std::shared_ptr<std::thread> thread_; // 智能指针,指向std::thread对象
pid_t tid_; // 线程的ID,在线程创建时再绑定
ThreadFunc func_; // 线程执行的回调函数
std::string name_; // 线程的名称
static std::atomic_int numCreated_; // 静态成员变量,用于统计创建的线程数量,使用std::atomic_int保证线程安全
};
}
2.4 Thread.cpp
cpp
#include "Thread.h"
#include "CurrentThread.h"
#include <semaphore.h>
using namespace mymuduo;
// 初始化静态成员变量,用于统计创建的线程数量
std::atomic_int Thread::numCreated_(0);
// 构造函数实现
Thread::Thread(ThreadFunc func, const std::string &name)
: started_(false) // 初始状态为未启动
, joined_(false) // 初始状态为未join
, tid_(0) // 初始线程ID为0
, func_(std::move(func)) // 移动语义转移线程执行的函数
, name_(name)
{
// 设置默认线程名称
setDefaultName();
}
// 析构函数实现
Thread::~Thread(){
// 如果线程已启动但未join,则分离线程
if (started_ && !joined_)
thread_->detach();
}
// 启动线程的方法实现
void Thread::start(){
started_ = true;
sem_t sem; // 定义信号量
/*
* &sem: 信号量对象
* false:表示线程间共享(非进程间)
* 0: 初始值(表示初始时无可用资源)
*/
sem_init(&sem, false, 0);
// 创建一个新的线程,使用lambda表达式作为线程执行体
thread_ = std::shared_ptr<std::thread>(new std::thread([&]() {
tid_ = CurrentThread::tid(); // 获取当前线程的ID
sem_post(&sem); // 信号量值+1,唤醒等待者,释放信号量,通知主线程已获取到线程ID
func_(); // 执行线程的回调函数
}));
sem_wait(&sem); // 主线程等待信号量,等待直到信号量>0,然后值-1
sem_destroy(&sem); // 销毁信号量
}
// 等待线程结束的方法实现
void Thread::join(){
joined_ = true;
thread_->join(); // 调用std::thread的join方法等待线程结束
}
// 设置默认线程名称的方法实现
void Thread::setDefaultName(){
int num = ++numCreated_; // 线程数量加1
if (name_.empty()){
char buf[32] = {0};
// 格式化线程名称,如Thread1, Thread2等
snprintf(buf, sizeof buf, "Thread%d", num);
name_ = buf;
}
}
3. CurrentThread 模块详解
3.1 thread_local
cpp
thread_local int t_cachedTid = 0;
thread_local 变量:每个线程 独立拥有 一份副本。
初始化为 0 的原因:
- 0 代表 "无效值"。
- Linux 的线程 ID 从 1 开始,不可能是 0。
- 所以:if (t_cachedTid == 0) 就意味着 "还没缓存过,需要去获取"。
3.2 __builtin_expect 分支预测优化
cpp
if (__builtin_expect(t_cachedTid == 0, 0)) {
cacheTid();
}
功能: 它告诉 CPU:括号里的条件大概率是假的,请按'假'的情况优化指令流水线。
- __builtin_expect(表达式,期望值)
- 这里 期望值 是 0(假)。
- 意思是:t_cachedTid == 0 这种情况 很少发生(只有线程第一次调用时)。
- t_cachedTid != 0 这种情况 经常发生(后续所有调用)。
原因: 现代 CPU 像一条 工厂流水线 ,它会 提前预测 下一步走哪条路,预先加载指令。
- 预测对了:流水线全速运转,速度极快。
- 预测错了 :流水线清空,重新加载,性能损失巨大(称为 分支预测失败惩罚)。
4. Thread 模块 start() 中的信号量同步
4.1 sem_init(&sem, false, 0)
cpp
sem_t sem;
sem_init(&sem, false, 0);
| 参数 | 含义 | 本例设置 |
|---|---|---|
| &sem | 信号量对象指针 | &sem |
| false | 是否进程间共享 | false(只线程间共享) |
| 0 | 初始值 | 0(关键! 表示初始无资源) |
4.2 sem_wait(&sem)
cpp
sem_wait(&sem); // 主线程调用
| 信号量值 | 动作 | 结果 |
|---|---|---|
| > 0 | 值 -1,立即返回 | 继续执行 |
| = 0 | 阻塞,进入睡眠 | 等待被唤醒 |
4.3 sem_post(&sem)
cpp
sem_post(&sem); // 子线程调用
| 动作 | 结果 |
|---|---|
| 信号量值 +1 | 0 → 1 |
| 唤醒一个等待的线程 | 主线程被唤醒 |
4.4 sem_destroy(&sem)
cpp
sem_destroy(&sem); // 主线程调用
- 释放信号量占用的资源
- 必须在 所有线程使用完后 调用
- 本例中,sem_wait() 返回后,同步已完成,可以安全销毁
4.5 时序
cpp
void Thread::start(){
started_ = true;
sem_t sem; // 创建信号量
sem_init(&sem, false, 0); // 初始值为 0(阻塞状态)
thread_ = std::shared_ptr<std::thread>(new std::thread([&]() {
tid_ = CurrentThread::tid(); // 1. 子线程获取自己的 ID
sem_post(&sem); // 2. 信号量 +1,通知主线程
func_(); // 3. 执行用户回调
}));
sem_wait(&sem); // 4. 主线程等待,直到信号量 > 0
sem_destroy(&sem); // 5. 销毁信号量
}
cpp
时间轴 →
主线程 子线程
│ │
│ start() │
│ │
│ sem_t sem; │
│ sem_init(&sem, false, 0); │
│ // sem 值 = 0 │
│ │
│ 创建子线程 ───────────────────────>│ 开始执行 lambda
│ │
│ sem_wait(&sem); │
│ // 检查 sem 值 = 0 │
│ // 阻塞,进入睡眠 │
│ │ tid_ = CurrentThread::tid();
│ │ // tid_ = 12345
│ │
│ │ sem_post(&sem);
│ │ // sem 值 0→1
│ │ // 唤醒主线程 ⏰
│ │
│ <────────── 被唤醒 ───────────────│
│ // sem_wait() 返回 │
│ // sem 值 1→0 │
│ │ func_();
│ │ // 执行用户回调
│ sem_destroy(&sem); │
│ // 销毁信号量 │
│ │
│ start() 返回 │ 继续运行...
│ │
│ printf("tid = %d", t.tid()); │
│ // 安全拿到 tid_ = 12345 │
│ │