C++八股文(多线程与并发)
1. C++ 中的线程(std::thread)如何创建和管理?
1. 创建线程: 使用std::thread构造函数传入可调用对象(函数、lambda、函数对象),线程创建后立即开始执行,可以传递参数给线程函数,参数默认按值拷贝可以用std::ref传引用。
2. 线程管理: 必须调用join()等待线程结束或detach()分离线程,未join或detach的线程析构时会调用std::terminate,joinable()检查线程是否可join,get_id()获取线程ID,hardware_concurrency()获取硬件并发数。
3. 参数传递: 参数按值拷贝到线程内部避免悬空引用,使用std::ref传递引用,使用std::move转移所有权,注意线程函数的参数生命周期必须长于线程执行时间。
4. 注意事项: 线程不可拷贝只能移动,异常安全需要确保join或detach被调用,避免在析构函数中启动线程,使用RAII封装线程管理(如jthread C++20)。
// 创建和管理线程
void task(int n) { std::cout << "Task " << n << std::endl; }
std::thread t1(task, 42); // 函数+参数
std::thread t2([]{ /* lambda */ }); // lambda
t1.join(); // 等待线程结束
t2.detach(); // 分离线程
// RAII封装
class ThreadGuard {
std::thread& t;
public:
ThreadGuard(std::thread& t_) : t(t_) {}
~ThreadGuard() { if(t.joinable()) t.join(); }
};
2. C++ 中的 std::mutex 和 std::lock_guard 是如何工作的?
1. std::mutex工作原理: 互斥锁提供独占访问保护临界区,lock()获取锁(阻塞直到成功),unlock()释放锁,try_lock()尝试获取锁立即返回,同一线程重复lock会死锁。
2. std::lock_guard特点: RAII封装的锁管理器,构造时自动加锁析构时自动解锁,不可拷贝不可移动,适合简单的作用域锁定,无法手动unlock只能等待析构。
3. std::unique_lock特点: 更灵活的锁管理器,可以手动lock/unlock,支持延迟加锁和条件变量,可以移动但不可拷贝,可以提前释放锁或转移所有权。
4. 使用建议: 优先使用lock_guard简单高效,需要灵活控制时使用unique_lock,锁的粒度尽可能小减少竞争,避免在持有锁时调用外部函数防止死锁。
std::mutex mtx;
int shared_data = 0;
// lock_guard:自动管理
void safe_increment() {
std::lock_guard<std::mutex> lock(mtx);
++shared_data;
} // 自动解锁
// unique_lock:灵活控制
void flexible_lock() {
std::unique_lock<std::mutex> lock(mtx);
++shared_data;
lock.unlock(); // 提前解锁
// 做其他不需要锁的工作
lock.lock(); // 重新加锁
}
3. C++ 中如何使用 std::condition_variable 进行线程同步?
1. 基本概念: 条件变量用于线程间的通知机制,一个线程等待条件满足另一个线程通知条件改变,必须配合mutex和unique_lock使用,避免虚假唤醒需要在循环中检查条件。
2. 主要操作: wait()释放锁并等待通知,被唤醒后重新获取锁,notify_one()唤醒一个等待线程,notify_all()唤醒所有等待线程,wait_for()和wait_until()支持超时等待。
3. 虚假唤醒: 线程可能在没有notify的情况下被唤醒,必须使用while循环检查条件而不是if,wait()的第二个参数可以传入谓词自动处理虚假唤醒。
4. 使用模式: 生产者通知消费者数据就绪,任务队列的等待和通知,线程池的任务分发,实现信号量和屏障等同步原语。
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
// 等待线程
void wait_thread() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 自动处理虚假唤醒
// 条件满足,继续执行
}
// 通知线程
void notify_thread() {
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
} // 先解锁再通知,避免等待线程立即阻塞
cv.notify_one();
}
4. C++ 中的原子操作(std::atomic)如何使用?
1. 基本概念: 原子操作保证操作的不可分割性,无需加锁实现线程安全,硬件级别的支持性能高于互斥锁,适用于简单的共享变量(计数器、标志位)。
2. 支持类型: 整数类型(int、long等)、指针类型、bool类型,自定义类型需要满足trivially copyable,常用操作有load、store、exchange、compare_exchange。
3. 内存序: memory_order_relaxed(最弱无同步),memory_order_acquire/release(获取释放语义),memory_order_seq_cst(顺序一致性默认最强),根据需求选择合适的内存序优化性能。
4. 使用场景: 无锁计数器、标志位、自旋锁实现、无锁数据结构、引用计数(shared_ptr内部使用),注意原子操作只保证单个变量的原子性不保证多个变量的一致性。
std::atomic<int> counter(0); std::atomic<bool> flag(false); // 原子操作 counter++; // 原子递增 counter.fetch_add(1); // 显式原子加法 int old = counter.exchange(100); // 原子交换 // CAS操作 int expected = 10; bool success = counter.compare_exchange_strong(expected, 20); // 如果counter==10则设为20返回true,否则expected被更新为counter的值 // 内存序优化 counter.store(42, std::memory_order_relaxed); int val = counter.load(std::memory_order_acquire);
5. C++ 中如何避免死锁?
1. 死锁条件: 互斥(资源独占)、持有并等待(持有资源同时等待其他资源)、不可抢占(资源不能被强制释放)、循环等待(线程间形成环形等待链),四个条件同时满足才会死锁。
2. 避免策略: 按固定顺序获取锁避免循环等待,使用std::lock同时获取多个锁,使用try_lock尝试获取失败则释放已持有的锁,设置超时机制避免无限等待。
3. 锁的层次: 为锁分配优先级总是按优先级顺序获取,使用lock_guard和unique_lock的RAII特性自动释放,避免在持有锁时调用外部函数或回调,减小锁的粒度和持有时间。
4. 检测和恢复: 使用死锁检测工具(ThreadSanitizer),设计时避免嵌套锁,使用无锁数据结构,实在需要多个锁时使用std::scoped_lock(C++17)一次性获取所有锁。
std::mutex m1, m2;
// 错误:可能死锁
void thread1() {
std::lock_guard<std::mutex> lock1(m1);
std::lock_guard<std::mutex> lock2(m2); // 线程2可能持有m2等待m1
}
// 正确:使用std::lock同时获取
void thread2() {
std::scoped_lock lock(m1, m2); // C++17,原子获取多个锁
}
// 正确:固定顺序
void thread3() {
std::lock_guard<std::mutex> lock1(m1); // 总是先m1后m2
std::lock_guard<std::mutex> lock2(m2);
}
6. 如何实现线程池?
1. 基本组件: 任务队列存储待执行任务,工作线程从队列取任务执行,互斥锁保护任务队列,条件变量通知线程有新任务,停止标志控制线程池关闭。
2. 工作流程: 初始化时创建固定数量的工作线程,线程循环等待任务队列有任务,提交任务时加入队列并通知等待线程,关闭时设置停止标志并通知所有线程退出。
3. 任务提交: 使用std::function存储任意可调用对象,使用std::packaged_task包装任务获取future,支持返回值和异常传递,使用完美转发传递参数。
4. 优化策略: 动态调整线程数量适应负载,任务优先级队列,线程亲和性绑定CPU核心,任务窃取(work stealing)平衡负载,避免任务队列无限增长设置上限。
class ThreadPool {
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex mtx;
std::condition_variable cv;
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。
查看14道真题和解析