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

    • 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++ 专业术语

多线程面试题与实战

定义与作用

多线程面试聚焦死锁排查、数据竞争检测、锁粒度优化和无锁编程概念。本章通过飞翔科技场景展示四个核心考点的实战解法。

核心原理

死锁四条件

数据竞争检测

完整示例

示例一:死锁案例与三种解法

场景说明:飞翔科技转账系统出现死锁,白歌用三种方案修复。

#include <iostream>
#include <mutex>
#include <thread>
#include <string>
#include <chrono>

class Account {
public:
    explicit Account(std::string name, int balance)
        : name_(std::move(name)), balance_(balance) {}

    std::string name_;
    int balance_;
    std::mutex mtx_;
};

// ❌ 死锁代码:两线程以相反顺序获取锁
void transferDeadlock(Account& from, Account& to, int amount) {
    std::lock_guard<std::mutex> lockFrom(from.mtx_);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));  // 加剧死锁
    std::lock_guard<std::mutex> lockTo(to.mtx_);

    from.balance_ -= amount;
    to.balance_   += amount;
}

// ✅ 方案一:std::lock 同时加多锁
void transferLock(Account& from, Account& to, int amount) {
    std::lock(from.mtx_, to.mtx_);  // 原子同时加锁,避免死锁
    std::lock_guard<std::mutex> lockFrom(from.mtx_, std::adopt_lock);
    std::lock_guard<std::mutex> lockTo  (to.mtx_,   std::adopt_lock);

    from.balance_ -= amount;
    to.balance_   += amount;
    std::cout << "  转账: " << from.name_ << " → " << to.name_
              << " ¥" << amount << "\n";
}

// ✅ 方案二:scoped_lock (C++17)
void transferScoped(Account& from, Account& to, int amount) {
    std::scoped_lock lock(from.mtx_, to.mtx_);

    from.balance_ -= amount;
    to.balance_   += amount;
}

// ✅ 方案三:固定锁顺序
void transferHierarchy(Account& from, Account& to, int amount) {
    // 总是先锁地址小的 mutex
    if (&from < &to) {
        std::lock_guard<std::mutex> l1(from.mtx_);
        std::lock_guard<std::mutex> l2(to.mtx_);
        from.balance_ -= amount;
        to.balance_   += amount;
    } else {
        std::lock_guard<std::mutex> l1(to.mtx_);
        std::lock_guard<std::mutex> l2(from.mtx_);
        from.balance_ -= amount;
        to.balance_   += amount;
    }
}

int main() {
    std::cout << "=== 飞翔科技转账系统 ===\n\n";

    Account a("大翔", 10000);
    Account b("孔蓝", 5000);

    // 方案一:std::lock
    std::cout << "--- std::lock 方案 ---\n";
    std::thread t1(transferLock, std::ref(a), std::ref(b), 1000);
    std::thread t2(transferLock, std::ref(b), std::ref(a), 500);
    t1.join(); t2.join();
    std::cout << a.name_ << " 余额: ¥" << a.balance_ << "\n";
    std::cout << b.name_ << " 余额: ¥" << b.balance_ << "\n";
}

预期输出:

=== 飞翔科技转账系统 ===

--- std::lock 方案 ---
  转账: 大翔 → 孔蓝 ¥1000
  转账: 孔蓝 → 大翔 ¥500
大翔 余额: ¥9500
孔蓝 余额: ¥5500

逐段分析:

  • std::lock(l1, l2, ...) 用死锁避免算法同时获取多个锁
  • std::adopt_lock 表示 mutex 已锁定,lock_guard 只负责解锁
  • std::scoped_lock (C++17) 是 lock_guard 的变参版,推荐使用
  • 固定锁顺序要求所有线程按同一顺序加锁

示例二:生产者-消费者与锁粒度

场景说明:李眉实现飞翔科技的异步日志队列。

#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <vector>
#include <string>
#include <chrono>

class AsyncLogger {
public:
    void produce(const std::string& msg) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            queue_.push(msg);
        }  // 锁在此释放,缩小临界区
        cv_.notify_one();
    }

    void consume() {
        while (running_) {
            std::unique_lock<std::mutex> lock(mtx_);
            cv_.wait(lock, [this] { return !queue_.empty() || !running_; });

            while (!queue_.empty()) {
                std::string msg = std::move(queue_.front());
                queue_.pop();
                lock.unlock();  // 处理时释放锁,缩小临界区

                // 模拟写入磁盘(耗时操作在锁外)
                std::this_thread::sleep_for(std::chrono::milliseconds(50));
                std::cout << "  [写入磁盘] " << msg << "\n";

                lock.lock();  // 重新获取锁继续处理
            }
        }
    }

    void stop() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            running_ = false;
        }
        cv_.notify_all();
    }

private:
    std::queue<std::string> queue_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool running_ = true;
};

int main() {
    std::cout << "=== 飞翔科技异步日志队列 ===\n\n";

    AsyncLogger logger;

    // 消费者线程
    std::thread consumer(&AsyncLogger::consume, &logger);

    // 模拟用户请求产生日志
    std::thread producers[3];
    std::string names[] = {"黄俪-前端", "小崔-后端", "白歌-架构"};

    for (int i = 0; i < 3; ++i) {
        producers[i] = std::thread([&logger, name = names[i]]() {
            for (int j = 1; j <= 3; ++j) {
                logger.produce(std::string(name) + ": 请求 #" + std::to_string(j));
                std::this_thread::sleep_for(std::chrono::milliseconds(30));
            }
        });
    }

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

    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    logger.stop();
    consumer.join();

    std::cout << "\n所有日志已写入磁盘\n";
}

锁粒度优化要点:

  • 临界区只保护共享数据,不保护耗时操作
  • 日志写入在 unlock() 之后执行,不阻塞生产者
  • condition_variable::wait 的 lambda 检查唤醒条件,处理虚假唤醒
  • 批量处理(while 循环取完队列)减少锁竞争

易错场景与面试考点

易错场景

1. 忘记 notify

void produce(int val) {
    std::lock_guard lock(mtx);
    queue.push(val);
}  // ❌ 忘记 cv.notify_one(),消费者可能永远等待

2. 条件变量虚假唤醒

// ❌ 用 if 而非 while
cv.wait(lock, []{ return ready; });  // ✅ 用 lambda 谓词自动处理

// 等价于
while (!ready)  // ✅ 手动 while 循环
    cv.wait(lock);

3. 锁的粒度不当

// ❌ 锁覆盖耗时操作
{
    std::lock_guard lock(mtx);
    auto data = queue.front(); queue.pop();
    writeToDisk(data);  // ❌ 耗时操作在锁内
}

// ✅ 先取出数据,再释放锁
std::string data;
{
    std::lock_guard lock(mtx);
    data = std::move(queue.front());
    queue.pop();
}
writeToDisk(data);  // ✅ 锁外操作

面试考点

考点要点
死锁四条件互斥、持有等待、不可抢占、循环等待——破坏任一即可预防
死锁预防std::lock 同时加锁、固定顺序、超时机制(try_lock_for)
数据竞争多线程无同步地访问同一内存且至少一个是写
条件变量wait 需 while 循环或用带谓词的 wait 重载
锁粒度临界区尽量小,耗时操作放锁外
无锁编程atomic + CAS(compare_exchange)实现无锁栈/队列
once_flagcall_once 保证初始化仅执行一次(替代 double-checked locking)
上一页
高频面试题精讲
下一页
内存管理常见陷阱与排查