Muduo:(3) 线程的封装,线程 ID 的获取、分支预测优化与信号量同步

1. 模块概述

1.1 CurrentThread

唯一核心目的:用最快的速度,获取当前线程的 ID。

1.2 Thread

线程封装类,提供跨平台的线程管理功能。Thread 的核心设计目标包括:

  1. 线程创建与管理:封装 std::thread 提供统一的线程接口
  2. 线程 ID 获取:提供获取真实线程 ID 的能力
  3. 线程命名:支持为线程设置名称便于调试
  4. 生命周期管理:自动管理线程的启动和销毁
功能 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           │
  │                                   │
相关推荐
仰泳的熊猫2 小时前
题目1523:蓝桥杯算法提高VIP-打水问题
数据结构·c++·算法·蓝桥杯
AxureMost2 小时前
产品经理:业务架构、应用架构与数据架构
架构·产品经理
汉克老师2 小时前
GESP2024年3月认证C++二级( 第三部分编程题(1) 乘法问题)
c++·算法·循环结构·gesp二级·gesp2级
白太岁2 小时前
Muduo:(0) 架构与接口总览
c++·架构·tcp
闻缺陷则喜何志丹2 小时前
微分中值定理与导数的应用
c++·高等数学·微分·中值定理·导数应用
tankeven2 小时前
HJ94 记票统计
c++·算法
mjhcsp3 小时前
C++ 树形 DP解析
开发语言·c++·动态规划·代理模式
犽戾武3 小时前
RK3588 上 ROS2 Humble + 串口机械臂驱动(ROS2安装流程 + V1版本Serial驱动)
c++
dgaf3 小时前
DX12 快速教程(15) —— 多实例渲染
c++·microsoft·图形渲染·visual studio·d3d12