C++ 并发编程 常考面试题总结
1. 如何使用std::mutex实现线程同步?
- 核心机制:std::mutex是互斥锁,用于保护临界区,保证同一时间只有一个线程执行临界区代码。
- 使用方式:直接使用lock()/unlock():需手动加锁解锁,易因异常导致死锁;推荐使用RAII包装器:std::lock_guard(自动加锁解锁,作用域结束自动释放)、std::unique_lock(支持延迟加锁、解锁、超时等待)。
- 代码:
#include <mutex>
#include <thread>
std::mutex mtx;
int shared_data = 0;
void increment() {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁
shared_data++;
} // 作用域结束,自动解锁
2. 如何避免死锁?举例说明一种策略
- 死锁产生条件:互斥、请求与保持、不剥夺、循环等待,破坏任一条件即可避免。
- 常见策略:按固定顺序加锁:所有线程按相同顺序获取锁,避免循环等待;超时加锁:使用std::unique_lock的try_lock_for()/try_lock_until(),超时后放弃并释放已持有的锁;避免嵌套锁:同一线程不重复获取同一锁;一次性获取所有锁:使用std::lock()同时获取多个锁,避免部分持有。
- (按固定顺序加锁):线程A和线程B都先锁mtx1再锁mtx2,避免循环等待。
3. 解释std::future和std::promise的用法
- 核心作用:实现线程间的异步结果传递,std::promise用于设置值,std::future用于获取值。
- 使用流程:线程A创建std::promise<T>,通过get_future()获取std::future<T>;线程A将std::promise<T>传递给线程B,线程B执行任务后调用set_value()设置结果;线程A通过std::future<T>的get()/wait()获取或等待结果。
- 代码:
#include <future>
#include <thread>
void task(std::promise<int>& p) {
p.set_value(42); // 设置结果
}
int main() {
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread t(task, std::ref(p));
int res = f.get(); // 获取结果
t.join();
return 0;
}
4. 如何实现线程池?
- 核心组件:线程队列:固定数量的工作线程,持续等待任务;任务队列:存储待执行的任务(函数对象/std::function);同步机制:互斥锁+条件变量,实现任务队列的线程安全和线程唤醒。
- 实现流程:初始化时创建N个工作线程,线程函数中循环等待任务;提交任务时,将任务加入队列,通过条件变量唤醒空闲线程;线程从队列中取出任务执行,执行完继续等待新任务;销毁时,设置终止标志,唤醒所有线程,等待线程结束。
- 关键特性:任务队列线程安全、线程复用、支持任务优先级(可选)。
5. std::atomic在C++11中如何实现原子操作?
- 核心原理:基于CPU提供的原子指令(如lock前缀、CAS指令),保证操作在CPU层面不可分割,避免多线程竞态条件。
- 实现方式:对内置类型(int、指针等)提供原子封装,支持load()/store()/fetch_add()等原子操作;支持内存序(std::memory_order),控制操作的可见性和重排序;无锁操作,无线程切换开销,性能高于互斥锁。
- :std::atomic<int> count = 0; count++;(原子自增,无需加锁)。
6. 解释什么是false sharing及其优化方法?
- false sharing定义:多个线程访问不同的变量,但这些变量位于同一个CPU缓存行,导致线程间频繁缓存失效,性能急剧下降。
- 优化方法:内存对齐填充:将变量按缓存行大小(通常64字节)对齐,避免多个变量共享缓存行;使用alignas关键字:指定变量对齐边界,如alignas(64) int x;;分离变量:将频繁修改的变量与其他变量分开存储,避免共享缓存行。
7. C++中线程的生命周期?
- 创建:通过std::thread构造函数创建线程,线程立即进入就绪状态;
- 运行:线程被调度执行,执行线程函数;
- 阻塞/等待:线程调用wait()/sleep_for()等,进入阻塞状态,等待条件满足;
- 终止:线程函数执行完毕,或调用std::terminate(),线程终止;
- 回收:通过join()等待线程结束并回收资源,或detach()分离线程,资源由系统自动回收。
8. 定义一个空类编译器做了哪些操作?
空类class Empty {};,编译器会默认生成6个成员函数:
- 默认构造函数;
- 拷贝构造函数;
- 拷贝赋值运算符;
- 析构函数;
- 移动构造函数(C++11,未显式定义拷贝/赋值/析构时生成);
- 移动赋值运算符(C++11,未显式定义拷贝/赋值/析构时生成)。
9. 友元函数和友元类
- 友元函数:在类中声明为friend的全局函数/其他类成员函数,可直接访问类的私有/保护成员,破坏封装性但提升灵活性;
- 友元类:在类中声明为friend的类,友元类的所有成员函数均可访问当前类的私有/保护成员;
- 注意点:友元关系是单向的,不可传递,不继承。
10. 什么情况下,类的析构函数应该声明为虚函数?为什么?
- 适用场景:类作为基类,且会通过基类指针/引用指向子类对象时,析构函数必须声明为虚函数;
- 核心原因:若析构函数非虚,通过基类指针delete子类对象时,仅调用基类析构函数,子类资源无法释放,引发内存泄漏;
- 效果:虚析构函数会触发动态绑定,先调用子类析构,再调用基类析构,保证资源完整释放。
11. 编写一个有构造函数,析构函数,赋值函数,和拷贝构造函数的String类?
#include <cstring>
#include <iostream>
class String {
public:
// 默认构造
String() : m_data(nullptr), m_size(0) {}
// 带参构造
String(const char* str) {
m_size = strlen(str);
m_data = new char[m_size + 1];
strcpy(m_data, str);
}
// 拷贝构造
String(const String& other) {
m_size = other.m_size;
m_data = new char[m_size + 1];
strcpy(m_data, other.m_data);
}
// 拷贝赋值
String& operator=(const String& other) {
if (this == &other) return *this;
delete[] m_data;
m_size = other.m_size;
m_data = new char[m_size + 1];
strcpy(m_data, other.m_data);
return *this;
}
// 析构
~String() {
delete[] m_data;
}
private:
char* m_data;
size_t m_size;
};
12. this指针的理解?
- 核心定义:this是类非静态成员函数的隐含指针,指向当前调用该函数的对象;
- 特性:类型为类名* const(指针本身不可修改,指向的对象可修改);静态成员函数没有this指针,因为静态函数属于类而非对象;可在成员函数中通过this->成员访问对象成员,解决命名冲突。
13. 程序加载时的内存分布?
- 代码段(Text):可执行代码、只读常量,共享且只读;
- 数据段(Data):已初始化的全局变量、静态变量;
- BSS段:未初始化的全局变量、静态变量,程序启动时自动清零;
- 堆区(Heap):new/malloc分配的内存,手动管理,向上生长;
- 栈区(Stack):局部变量、函数参数、返回地址,自动管理,向下生长;
- 内核空间:操作系统内核代码和数据,用户进程不可直接访问。
14. 智能指针?
- 核心定义:封装原始指针的类模板,基于RAII机制自动管理堆内存,避免内存泄漏;
- 常用类型:unique_ptr:独占所有权,不可拷贝,可移动,离开作用域自动释放;shared_ptr:共享所有权,通过引用计数管理,线程安全,计数为0时释放;weak_ptr:辅助shared_ptr,不增加引用计数,解决循环引用问题;
- 优势:自动释放内存,避免手动delete,减少内存泄漏风险。
15. vector扩容原理说明?
- 扩容触发:当size() == capacity()时,插入新元素触发扩容;
- 扩容策略:通常按2倍(或1.5倍)增长,分配新的更大内存,将原元素拷贝/移动到新内存,释放原内存;
- 影响:扩容会导致迭代器失效,因为内存地址发生变化;
- 优化:提前调用reserve(n)预分配足够内存,避免频繁扩容。
16. 内联函数与普通函数的区别?
C++面试总结 文章被收录于专栏
本专栏系统梳理C++面试高频考点,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力。