乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

unique_lock 与 condition_variable

定义与作用

unique_lock 是灵活的 RAII 锁包装器,支持延迟加锁、提前解锁和锁转移。condition_variable 提供线程间等待/通知机制。

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

// 等待线程
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });  // 等待直到 ready==true

// 通知线程
{
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
}
cv.notify_one();

核心原理

条件变量工作流

unique_lock vs lock_guard

完整示例

示例一:飞翔科技任务队列(生产者-消费者)

场景说明:白歌实现飞翔科技后台任务调度系统。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <string>
#include <chrono>
#include <random>
#include <sstream>

std::mutex coutMutex;  // 仅用于同步输出

class TaskQueue {
public:
    void push(const std::string& task) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            queue_.push(task);
        }
        cv_.notify_one();  // 通知一个等待的消费者
    }

    std::string pop() {
        std::unique_lock<std::mutex> lock(mtx_);

        // 带谓词的 wait:自动处理虚假唤醒
        cv_.wait(lock, [this] { return !queue_.empty() || stopped_; });

        if (stopped_ && queue_.empty())
            return "";  // 停止信号

        auto task = queue_.front();
        queue_.pop();
        return task;
    }

    void stop() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            stopped_ = true;
        }
        cv_.notify_all();  // 唤醒所有等待线程以退出
    }

    size_t size() const {
        std::lock_guard<std::mutex> lock(mtx_);
        return queue_.size();
    }

private:
    mutable std::mutex mtx_;
    std::condition_variable cv_;
    std::queue<std::string> queue_;
    bool stopped_ = false;
};

void producer(TaskQueue& q, int id) {
    std::vector<std::string> tasks = {
        "编译项目", "运行测试", "部署服务",
        "备份数据库", "清理日志", "发送报告",
    };

    std::mt19937 rng(id);
    std::uniform_int_distribution<int> dist(100, 500);

    for (const auto& t : tasks) {
        std::this_thread::sleep_for(std::chrono::milliseconds(dist(rng)));

        std::ostringstream oss;
        oss << "[P" << id << "] " << t;
        q.push(oss.str());

        {
            std::lock_guard<std::mutex> lock(coutMutex);
            std::cout << "  生产者 " << id << " 发布: " << t
                      << " (队列: " << q.size() << ")\n";
        }
    }
}

void consumer(TaskQueue& q, int id) {
    std::mt19937 rng(id + 100);
    std::uniform_int_distribution<int> dist(200, 800);

    while (true) {
        auto task = q.pop();
        if (task.empty()) break;  // 停止信号

        {
            std::lock_guard<std::mutex> lock(coutMutex);
            std::cout << "消费者 " << id << " 开始: " << task << "\n";
        }

        // 模拟处理任务
        std::this_thread::sleep_for(std::chrono::milliseconds(dist(rng)));

        {
            std::lock_guard<std::mutex> lock(coutMutex);
            std::cout << "消费者 " << id << " 完成: " << task << "\n";
        }
    }
}

int main() {
    std::cout << "=== 飞翔科技任务调度系统 ===\n\n";

    TaskQueue queue;

    std::vector<std::thread> producers;
    for (int i = 1; i <= 3; ++i)
        producers.emplace_back(producer, std::ref(queue), i);

    std::thread c1(consumer, std::ref(queue), 1);
    std::thread c2(consumer, std::ref(queue), 2);

    for (auto& t : producers) t.join();

    // 所有生产者完成,发送停止信号
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "\n所有任务已发布,发送停止信号...\n";
    queue.stop();

    c1.join();
    c2.join();

    std::cout << "所有消费者已退出,调度系统关闭\n";
}

预期输出:

=== 飞翔科技任务调度系统 ===

  生产者 1 发布: 编译项目 (队列: 1)
  消费者 1 开始: [P1] 编译项目
  生产者 2 发布: 编译项目 (队列: 1)
  消费者 2 开始: [P2] 编译项目
  ...
  消费者 1 完成: [P1] 编译项目
  ...

所有任务已发布,发送停止信号...
所有消费者已退出,调度系统关闭

逐段分析:

  • cv.wait(lock, pred) 等价于 while(!pred()) cv.wait(lock),自动处理虚假唤醒
  • notify_one() 唤醒一个等待线程;notify_all() 唤醒所有
  • unique_lock 在 wait 时可以自动释放/重新获取锁
  • stopped_ 标志 + notify_all() 是优雅关闭的经典模式

示例二:虚假唤醒演示与对比

场景说明:黄俪演示为什么带谓词的 wait 是必要的。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> data;
bool ready = false;

// ❌ 错误方式:容易受虚假唤醒影响
void consumerWrong(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    if (data.empty()) {           // if 而非 while
        cv.wait(lock);            // 不带谓词
    }
    int val = data.front();
    data.pop();
    std::cout << "消费者 " << id << " (错误) 拿到: " << val << "\n";
}

// ✅ 正确方式:带谓词的 wait
void consumerRight(int id) {
    std::unique_lock<std::mutex> lock(mtx);

    // 带谓词:等价于 while(!pred) wait()
    cv.wait(lock, [] { return !data.empty(); });

    int val = data.front();
    data.pop();
    std::cout << "消费者 " << id << " (正确) 拿到: " << val << "\n";
}

int main() {
    std::cout << "=== 虚假唤醒对比 ===\n\n";

    // 演示正确方式
    std::cout << "--- 正确方式 ---\n";
    std::thread c1(consumerRight, 1);

    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    {
        std::lock_guard<std::mutex> lock(mtx);
        data.push(42);
    }
    cv.notify_one();
    c1.join();

    // 总结
    std::cout << "\n--- 关键区别 ---\n";
    std::cout << "错误: 使用 if 检查条件 + 无谓词 wait\n";
    std::cout << "  - 虚假唤醒后不会重新检查条件\n";
    std::cout << "  - 可能访问空队列导致未定义行为\n";
    std::cout << "正确: 使用带谓词的 cv.wait(lock, pred)\n";
    std::cout << "  - 内部等价于 while(!pred()) wait(lock)\n";
    std::cout << "  - 自动处理虚假唤醒\n";
}

预期输出:

=== 虚假唤醒对比 ===

--- 正确方式 ---
消费者 1 (正确) 拿到: 42

--- 关键区别 ---
错误: 使用 if 检查条件 + 无谓词 wait
  - 虚假唤醒后不会重新检查条件
  - 可能访问空队列导致未定义行为
正确: 使用带谓词的 cv.wait(lock, pred)
  - 内部等价于 while(!pred()) wait(lock)
  - 自动处理虚假唤醒

逐段分析:

  • 虚假唤醒是 POSIX 规范允许的行为:即使没有 notify,wait 也可能返回
  • cv.wait(lock, pred) 内部实现为 while(!pred()) wait(lock)
  • 永远使用带谓词的 wait,除非有特殊原因

易错场景与面试考点

易错场景

1. 不带谓词的 wait

// ❌
cv.wait(lock);  // 虚假唤醒后不检查条件

// ✅
cv.wait(lock, [] { return ready; });

2. 在 lock 外修改共享数据

// ❌
{
    std::lock_guard lock(mtx);
    data.push(x);
}  // 解锁
cv.notify_one();  // 在解锁后通知 → 被通知线程可能看到旧状态

// ✅ 解锁后通知也安全,因为 wait 会重新检查谓词
// 但在锁内通知可避免"惊群"开销
{
    std::lock_guard lock(mtx);
    data.push(x);
    cv.notify_one();  // 锁内通知(建议)
}

3. 忘记处理停止条件

// ❌ 消费者永远等待
cv.wait(lock, [] { return !queue.empty(); });  // 队列空时永久阻塞

// ✅ 加上停止标志
cv.wait(lock, [] { return !queue.empty() || stopped; });

面试考点

考点要点
unique_lock vs lock_guardunique_lock 更灵活:延迟加锁、手动解锁、可转移
condition_variable等待/通知机制,必须配合 mutex 使用
wait 两种形式无谓词(需手动 while)、带谓词(推荐)
虚假唤醒OS 可能无故唤醒,必须 while 检查条件
notify_one vs notify_all一个 vs 全部等待线程
生产者消费者模式经典并发模型,条件变量 + 队列
unique_lock 构造选项adopt_lock / defer_lock / try_to_lock
unique_lock 可移动支持 move,不可 copy
上一页
mutex 与 lock_guard
下一页
thread_local 线程局部存储