目录

一、 CurrentThread.h

1. CountDownLatch 的整体定位

2. 逐模块拆解核心实现

3. 关键细节深度解析

1. 核心成员变量的设计逻辑

4. 核心使用场景示例

场景 1:线程启动同步

场景 2:多任务完成等待

5. 设计亮点与注意事项

设计亮点

注意事项

二、 CurrentThread.cc

1. 代码整体核心逻辑回顾

2. 逐函数拆解核心实现

1. 构造函数:初始化同步资源

2. wait():阻塞等待倒计时结束

3. countDown():倒计时减 1,触发唤醒

4. getCount():线程安全获取当前计数

3. 核心设计亮点总结

4. 典型执行流程(多线程场景)

总结


一、 CurrentThread.h

先贴出完整代码,再逐部分解释:

// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
//
// 作者:陈硕 (chenshuo at chenshuo dot com)

#ifndef MUDUO_BASE_COUNTDOWNLATCH_H
#define MUDUO_BASE_COUNTDOWNLATCH_H

#include "muduo/base/Condition.h"  // 条件变量(配合互斥锁实现线程等待/唤醒)
#include "muduo/base/Mutex.h"      // 互斥锁(保证计数操作的线程安全)

namespace muduo
{

// 倒计时门闩(不可拷贝)
// 核心功能:一种同步工具,允许一个/多个线程阻塞等待,直到其他线程完成指定数量的操作(计数减至 0)
// 典型场景:主线程等待所有子线程初始化完成、多个线程等待某个事件触发
class CountDownLatch : noncopyable
{
 public:
  // 构造函数:初始化倒计时计数
  // @param count 初始计数(必须 >= 0,计数为 0 时 wait 不会阻塞)
  explicit CountDownLatch(int count);

  // 阻塞等待:当前线程阻塞,直到计数减至 0
  // 若调用时计数已为 0,直接返回;否则释放锁并阻塞,直到被 countDown 唤醒且计数为 0
  void wait();

  // 计数递减:将计数减 1,若减至 0 则唤醒所有等待在该门闩上的线程
  void countDown();

  // 获取当前剩余计数(线程安全)
  int getCount() const;

 private:
  mutable MutexLock mutex_;          // 互斥锁(mutable 允许 const 成员函数加锁)
  Condition condition_ GUARDED_BY(mutex_); // 条件变量(受 mutex_ 保护,用于等待/唤醒)
  int count_ GUARDED_BY(mutex_);           // 剩余计数(受 mutex_ 保护,确保原子修改)
};

}  // namespace muduo
#endif  // MUDUO_BASE_COUNTDOWNLATCH_H

1. CountDownLatch 的整体定位

  • 一个或多个线程阻塞等待,直到其他线程完成指定数量的操作(倒计时计数 count 减到 0);
  • 基于 MutexLock(互斥锁)+ Condition(条件变量)实现,符合 POSIX 线程同步规范;
  • 语义清晰:countDown() 是 “倒计时减 1”,wait() 是 “等待倒计时结束”,极易理解和使用;
  • 典型使用场景:
    1. 线程启动同步(如 Thread 类中 count=1,子线程 countDown(),主线程 wait() 确保子线程初始化完成);
    2. 多任务等待(如 count=N,N 个工作线程各 countDown() 一次,主线程 wait() 直到所有任务完成);
    3. 资源初始化等待(如主线程等待多个子线程完成资源加载后再继续)。

2. 逐模块拆解核心实现

class CountDownLatch : noncopyable  // 禁止拷贝:关联互斥锁+条件变量,拷贝会导致资源管理混乱
{
 public:
  // 构造函数:显式构造(避免int隐式转换为CountDownLatch),接收初始倒计时数
  explicit CountDownLatch(int count);

  // 核心接口1:阻塞当前线程,直到count_减到0
  void wait();

  // 核心接口2:倒计时数减1;若减到0,唤醒所有等待的线程
  void countDown();

  // 核心接口3:获取当前倒计时数(线程安全)
  int getCount() const;

 private:
  // 成员变量(线程安全设计是核心)
  mutable MutexLock mutex_;          // mutable:允许const成员函数(如getCount)修改它(加锁)
  Condition condition_ GUARDED_BY(mutex_);  // 条件变量,受mutex_保护(Clang线程安全注解)
  int count_ GUARDED_BY(mutex_);            // 倒计时计数,受mutex_保护(确保线程安全修改/读取)
};

3. 关键细节深度解析

1. 核心成员变量的设计逻辑
成员变量 特性 核心作用 设计原因
mutable MutexLock mutex_ mutable 修饰 保护 count_condition_ 的线程安全访问 getCount()const 成员函数,需要加锁访问 count_mutable 允许 const 函数修改互斥锁(加锁 / 解锁)
Condition condition_ GUARDED_BY(mutex_) Clang 注解 实现线程阻塞 / 唤醒 GUARDED_BY(mutex_) 告诉 Clang 编译器:访问 condition_ 必须先持有 mutex_,编译期检查违规操作
int count_ GUARDED_BY(mutex_) Clang 注解 倒计时核心计数 GUARDED_BY(mutex_) 确保所有对 count_ 的读写都在 mutex_ 保护下,避免数据竞争

4. 核心使用场景示例

场景 1:线程启动同步
// 主线程
CountDownLatch latch(1); // 初始count=1
Thread t([&latch]() {
  // 子线程初始化(如设置tid、线程名)
  latch.countDown(); // count减到0,唤醒主线程
  // 执行线程逻辑
});

t.start();
latch.wait(); // 主线程阻塞,直到子线程countDown()
// 此时子线程已完成初始化,主线程可安全访问子线程的tid等信息
场景 2:多任务完成等待
const int kTaskNum = 5;
CountDownLatch latch(kTaskNum); // 初始count=5

// 启动5个工作线程
for (int i = 0; i < kTaskNum; ++i)
{
  Thread t([&latch, i]() {
    // 执行任务
    printf("Task %d done\n", i);
    latch.countDown(); // 每个任务完成后count减1
  });
  t.start();
}

latch.wait(); // 主线程等待所有5个任务完成
printf("All tasks done\n");

5. 设计亮点与注意事项

设计亮点
  1. 线程安全极致
    • 所有对 count_ 的访问都加锁,避免数据竞争;
    • 条件变量的 wait/notify 都在锁保护下,符合 POSIX 规范;
    • Clang 注解编译期检查锁的使用,提前发现违规。
  2. 语义极简清晰
    • wait() = 等倒计时结束,countDown() = 倒计时 - 1,无需额外文档即可理解;
    • 无冗余接口,仅保留核心功能,符合 Muduo “极简高效” 的设计理念。
  3. 防虚假唤醒
    • wait() 中用 while 循环检查 count_,彻底规避条件变量的虚假唤醒问题。
注意事项
  1. 不可重复使用
    • CountDownLatch 的 count 减到 0 后,无法重置(无 reset 接口),若需重复使用需重新创建对象;
  2. 禁止拷贝
    • 继承 noncopyable,避免拷贝导致多个对象共享同一个 mutex/condition,引发死锁 / 未定义行为;
  3. count 初始值
    • 构造时 count 必须 ≥0,否则 wait() 会立即返回(无意义)。

二、 CurrentThread.cc

先贴出完整代码,再逐部分解释:

// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
//
// 作者:陈硕 (chenshuo at chenshuo dot com)

#include "muduo/base/CountDownLatch.h"

using namespace muduo;

// 构造函数:初始化倒计时门闩
// @param count 初始计数(>=0),计数为0时wait()不会阻塞
CountDownLatch::CountDownLatch(int count)
  : mutex_(),               // 初始化互斥锁(默认构造)
    condition_(mutex_),     // 初始化条件变量,绑定当前互斥锁
    count_(count)           // 初始化剩余计数
{
}

// 阻塞等待:直到计数减至0
void CountDownLatch::wait()
{
  MutexLockGuard lock(mutex_); // RAII加锁,作用域结束自动解锁
  // 必须使用while循环:处理条件变量的「虚假唤醒」
  // 即使被唤醒,仍需检查count_是否为0,避免无意义的唤醒
  while (count_ > 0)
  {
    // 释放锁并阻塞,直到被notifyAll()唤醒且重新获取锁
    condition_.wait();
  }
}

// 计数递减:将计数减1,若减至0则唤醒所有等待线程
void CountDownLatch::countDown()
{
  MutexLockGuard lock(mutex_); // RAII加锁,保证count_修改的线程安全
  --count_;                    // 计数原子减1(加锁下操作,无竞态)
  if (count_ == 0)             // 计数减至0,所有等待线程可继续执行
  {
    // 唤醒所有等待在该条件变量上的线程(而非单个)
    // 保证所有等待线程都能感知到计数已为0
    condition_.notifyAll();
  }
}

// 获取当前剩余计数(线程安全)
// const成员函数:承诺不修改对象逻辑状态,但仍需加锁保证读取安全
int CountDownLatch::getCount() const
{
  MutexLockGuard lock(mutex_); // mutable修饰的mutex_允许const函数加锁
  return count_;               // 加锁后读取,避免并发修改导致的脏读
}

1. 代码整体核心逻辑回顾

这个实现是 CountDownLatch.h 声明的接口的具体落地,核心遵循 “互斥锁保护共享变量 + 条件变量实现阻塞 / 唤醒” 的经典同步模式,所有对共享变量 count_ 的访问都在锁保护下,确保多线程环境下的原子性和可见性。

2. 逐函数拆解核心实现

1. 构造函数:初始化同步资源
CountDownLatch::CountDownLatch(int count)
  : mutex_(),          // 初始化互斥锁(MutexLock默认构造即可,无需参数)
    condition_(mutex_),// 条件变量绑定互斥锁(必须配对,POSIX规范要求)
    count_(count)      // 初始化倒计时计数(由用户指定初始值)
{
}

关键设计点

  • condition_(mutex_):条件变量 Condition 的构造函数必须接收 MutexLock&,这是 Muduo 封装的规范 —— 每个条件变量必须绑定一个互斥锁,确保 wait() 时能原子释放锁,notify 时能保证共享变量的可见性;
  • 无额外校验:Muduo 假设用户传入的 count ≥ 0(若传入负数,wait() 会直接返回,无实际意义,但不会触发崩溃);
  • 初始化顺序:按成员声明顺序初始化(mutex_condition_count_),符合 C++ 构造函数初始化列表的规则(避免未初始化的 mutex 传给 condition)。
2. wait():阻塞等待倒计时结束
void CountDownLatch::wait()
{
  // 核心1:RAII加锁——构造时加锁,函数退出(无论正常/异常)时自动解锁,避免锁泄漏
  MutexLockGuard lock(mutex_);
  
  // 核心2:while循环而非if——防条件变量的「虚假唤醒」
  while (count_ > 0)
  {
    // condition_.wait() 做两件事(原子操作):
    // 1. 释放 mutex_ 锁,让其他线程能修改 count_;
    // 2. 阻塞当前线程,直到被 notifyAll() 唤醒;
    // 唤醒后会重新加锁,然后再次进入while判断 count_ 是否真的为0
    condition_.wait();
  }
}

必须理解的核心细节

  • 为什么用 while 而非 if?条件变量的 wait() 可能被 “虚假唤醒”(比如系统信号、内核调度触发,而非 notifyAll()),如果用 if (count_ > 0),虚假唤醒会直接退出循环,导致线程在 count_ 未到 0 时提前继续执行,破坏同步语义;而 while 会重新检查 count_,只有当 count_ == 0 时才退出,彻底规避虚假唤醒。
  • RAII 锁的必要性MutexLockGuard 是栈上对象,函数退出时(包括 wait() 阻塞后被唤醒、或异常退出)会自动调用析构函数解锁,避免手动加解锁导致的锁泄漏(比如忘记 unlock、异常跳过 unlock)。
3. countDown():倒计时减 1,触发唤醒
void CountDownLatch::countDown()
{
  // RAII加锁:确保 count_-- 是原子操作
  MutexLockGuard lock(mutex_);
  
  --count_; // 倒计时减1(锁保护下的原子操作)
  
  // 只有当count减到0时,才唤醒所有等待的线程
  if (count_ == 0)
  {
    // 选择 notifyAll() 而非 notifyOne():
    // 保证所有等待的线程都被唤醒(比如多个线程同时wait()),符合「倒计时结束后所有等待者继续」的语义
    condition_.notifyAll();
  }
}

关键设计点

  • --count_ 的原子性:在 MutexLockGuard 保护下,多线程调用 countDown() 时,count_ 的递减是原子的,不会出现 “多个线程同时减 1 导致计数错误” 的情况;
  • notifyAll() 的选择:如果用 notifyOne(),当多个线程同时 wait() 时,可能只有一个线程被唤醒,其余线程会永久阻塞(因为 count_ 已经是 0,不会再触发 notify),而 notifyAll() 能唤醒所有等待线程,是更安全的选择(代价是轻微的上下文切换,但倒计时场景下 count 到 0 仅一次,代价可忽略);
  • 无负数处理:即使多次调用 countDown() 导致 count_ < 0,也不会触发额外 notify(只有 count_ == 0 时才 notify),不影响语义。
4. getCount():线程安全获取当前计数
int CountDownLatch::getCount() const
{
  // 核心:mutable 修饰的 mutex_ 允许 const 函数加锁
  MutexLockGuard lock(mutex_);
  return count_; // 锁保护下读取,保证可见性和原子性
}

关键设计点

  • const 成员函数:getCount() 仅读取 count_,不修改对象的逻辑状态,符合 const 语义;
  • mutable mutex_ 的支撑:MutexLockGuard 构造时会修改 mutex_(加锁),而 const 函数默认不允许修改成员变量,mutex_mutable 修饰后,才能在 const 函数中加锁;
  • 锁保护的必要性:即使是 “只读” 操作,多线程环境下也需要加锁 —— 一方面保证 count_ 的读取是原子的(避免读到半更新的值),另一方面通过锁的内存屏障保证 count_ 的可见性(其他线程修改的 count_ 能被当前线程立即看到)。

3. 核心设计亮点总结

设计点 作用 为什么重要
RAII 锁管理(MutexLockGuard) 自动加锁 / 解锁,避免锁泄漏 手动 unlock 易因异常 / 分支遗漏导致死锁,RAII 是 C++ 线程安全的基石
while 循环防虚假唤醒 确保只有 count_=0 时才退出等待 条件变量虚假唤醒是 POSIX 规范允许的,必须显式检查共享变量
notifyAll () 而非 notifyOne () 唤醒所有等待线程 倒计时场景下 count_=0 仅一次,需保证所有等待者都被唤醒,避免线程永久阻塞
mutable + const 函数加锁 线程安全读取计数,不破坏 const 语义 既保证接口的 “只读” 语义,又保证多线程下的安全访问
条件变量绑定互斥锁 符合 POSIX 规范,保证同步正确性 条件变量的 wait () 必须在锁保护下,否则会导致未定义行为

4. 典型执行流程(多线程场景)

假设主线程初始化 CountDownLatch latch(2),启动两个子线程:

  1. 主线程调用 latch.wait() → 加锁后发现 count_=2>0 → 调用 condition_.wait() 释放锁并阻塞;
  2. 子线程 1 调用 latch.countDown() → 加锁后 count_=1 → 未到 0,解锁返回;
  3. 子线程 2 调用 latch.countDown() → 加锁后 count_=0 → 调用 condition_.notifyAll() 唤醒主线程 → 解锁返回;
  4. 主线程被唤醒 → 重新加锁 → while 检查 count_=0 → 退出循环,wait() 返回,继续执行。

总结

  1. 锁管理:全程使用 MutexLockGuard 的 RAII 机制,杜绝锁泄漏和死锁风险;
  2. 条件变量使用wait() 必须在锁保护下,且用 while 循环检查共享变量,notifyAll() 保证所有等待线程被唤醒;
  3. 线程安全:所有对 count_ 的读写都在锁保护下,const 函数通过 mutable 加锁实现安全读取;
  4. 极简设计:无冗余代码,每一行都服务于 “倒计时同步” 的核心语义,符合 Muduo “高效、简洁、正确” 的设计理念。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐