C++ 并发编程 常考面试题总结

1. 如何使用std::mutex实现线程同步?

  1. 核心机制:std::mutex是互斥锁,用于保护临界区,保证同一时间只有一个线程执行临界区代码。
  2. 使用方式:直接使用lock()/unlock():需手动加锁解锁,易因异常导致死锁;推荐使用RAII包装器:std::lock_guard(自动加锁解锁,作用域结束自动释放)、std::unique_lock(支持延迟加锁、解锁、超时等待)。
  3. 代码:
#include <mutex>
#include <thread>

std::mutex mtx;
int shared_data = 0;

void increment() {
    std::lock_guard<std::mutex> lock(mtx); // 自动加锁
    shared_data++;
} // 作用域结束,自动解锁

2. 如何避免死锁?举例说明一种策略

  1. 死锁产生条件:互斥、请求与保持、不剥夺、循环等待,破坏任一条件即可避免。
  2. 常见策略:按固定顺序加锁:所有线程按相同顺序获取锁,避免循环等待;超时加锁:使用std::unique_lock的try_lock_for()/try_lock_until(),超时后放弃并释放已持有的锁;避免嵌套锁:同一线程不重复获取同一锁;一次性获取所有锁:使用std::lock()同时获取多个锁,避免部分持有。
  3. (按固定顺序加锁):线程A和线程B都先锁mtx1再锁mtx2,避免循环等待。

3. 解释std::future和std::promise的用法

  1. 核心作用:实现线程间的异步结果传递,std::promise用于设置值,std::future用于获取值。
  2. 使用流程:线程A创建std::promise<T>,通过get_future()获取std::future<T>;线程A将std::promise<T>传递给线程B,线程B执行任务后调用set_value()设置结果;线程A通过std::future<T>的get()/wait()获取或等待结果。
  3. 代码:
#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. 如何实现线程池?

  1. 核心组件:线程队列:固定数量的工作线程,持续等待任务;任务队列:存储待执行的任务(函数对象/std::function);同步机制:互斥锁+条件变量,实现任务队列的线程安全和线程唤醒。
  2. 实现流程:初始化时创建N个工作线程,线程函数中循环等待任务;提交任务时,将任务加入队列,通过条件变量唤醒空闲线程;线程从队列中取出任务执行,执行完继续等待新任务;销毁时,设置终止标志,唤醒所有线程,等待线程结束。
  3. 关键特性:任务队列线程安全、线程复用、支持任务优先级(可选)。

5. std::atomic在C++11中如何实现原子操作?

  1. 核心原理:基于CPU提供的原子指令(如lock前缀、CAS指令),保证操作在CPU层面不可分割,避免多线程竞态条件。
  2. 实现方式:对内置类型(int、指针等)提供原子封装,支持load()/store()/fetch_add()等原子操作;支持内存序(std::memory_order),控制操作的可见性和重排序;无锁操作,无线程切换开销,性能高于互斥锁。
  3. :std::atomic<int> count = 0; count++;(原子自增,无需加锁)。

6. 解释什么是false sharing及其优化方法?

  1. false sharing定义:多个线程访问不同的变量,但这些变量位于同一个CPU缓存行,导致线程间频繁缓存失效,性能急剧下降。
  2. 优化方法:内存对齐填充:将变量按缓存行大小(通常64字节)对齐,避免多个变量共享缓存行;使用alignas关键字:指定变量对齐边界,如alignas(64) int x;;分离变量:将频繁修改的变量与其他变量分开存储,避免共享缓存行。

7. C++中线程的生命周期?

  1. 创建:通过std::thread构造函数创建线程,线程立即进入就绪状态;
  2. 运行:线程被调度执行,执行线程函数;
  3. 阻塞/等待:线程调用wait()/sleep_for()等,进入阻塞状态,等待条件满足;
  4. 终止:线程函数执行完毕,或调用std::terminate(),线程终止;
  5. 回收:通过join()等待线程结束并回收资源,或detach()分离线程,资源由系统自动回收。

8. 定义一个空类编译器做了哪些操作?

空类class Empty {};,编译器会默认生成6个成员函数:

  1. 默认构造函数;
  2. 拷贝构造函数;
  3. 拷贝赋值运算符;
  4. 析构函数;
  5. 移动构造函数(C++11,未显式定义拷贝/赋值/析构时生成);
  6. 移动赋值运算符(C++11,未显式定义拷贝/赋值/析构时生成)。

9. 友元函数和友元类

  1. 友元函数:在类中声明为friend的全局函数/其他类成员函数,可直接访问类的私有/保护成员,破坏封装性但提升灵活性;
  2. 友元类:在类中声明为friend的类,友元类的所有成员函数均可访问当前类的私有/保护成员;
  3. 注意点:友元关系是单向的,不可传递,不继承。

10. 什么情况下,类的析构函数应该声明为虚函数?为什么?

  1. 适用场景:类作为基类,且会通过基类指针/引用指向子类对象时,析构函数必须声明为虚函数;
  2. 核心原因:若析构函数非虚,通过基类指针delete子类对象时,仅调用基类析构函数,子类资源无法释放,引发内存泄漏;
  3. 效果:虚析构函数会触发动态绑定,先调用子类析构,再调用基类析构,保证资源完整释放。

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指针的理解?

  1. 核心定义:this是类非静态成员函数的隐含指针,指向当前调用该函数的对象;
  2. 特性:类型为类名* const(指针本身不可修改,指向的对象可修改);静态成员函数没有this指针,因为静态函数属于类而非对象;可在成员函数中通过this->成员访问对象成员,解决命名冲突。

13. 程序加载时的内存分布?

  1. 代码段(Text):可执行代码、只读常量,共享且只读;
  2. 数据段(Data):已初始化的全局变量、静态变量;
  3. BSS段:未初始化的全局变量、静态变量,程序启动时自动清零;
  4. 堆区(Heap):new/malloc分配的内存,手动管理,向上生长;
  5. 栈区(Stack):局部变量、函数参数、返回地址,自动管理,向下生长;
  6. 内核空间:操作系统内核代码和数据,用户进程不可直接访问。

14. 智能指针?

  1. 核心定义:封装原始指针的类模板,基于RAII机制自动管理堆内存,避免内存泄漏;
  2. 常用类型:unique_ptr:独占所有权,不可拷贝,可移动,离开作用域自动释放;shared_ptr:共享所有权,通过引用计数管理,线程安全,计数为0时释放;weak_ptr:辅助shared_ptr,不增加引用计数,解决循环引用问题;
  3. 优势:自动释放内存,避免手动delete,减少内存泄漏风险。

15. vector扩容原理说明?

  1. 扩容触发:当size() == capacity()时,插入新元素触发扩容;
  2. 扩容策略:通常按2倍(或1.5倍)增长,分配新的更大内存,将原元素拷贝/移动到新内存,释放原内存;
  3. 影响:扩容会导致迭代器失效,因为内存地址发生变化;
  4. 优化:提前调用reserve(n)预分配足够内存,避免频繁扩容。

16. 内联函数与普通函数的区别?

C++面试总结 文章被收录于专栏

本专栏系统梳理C++面试高频考点,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力。

全部评论

相关推荐

子夏2024:小米果然是花小钱办大事,十块 100块的红包搞这么大排场,给人一种很亲人的感觉,还让员工感动上了,我度发的基本都是365,588这种,也没见老板们搞什么煽情让给员工感恩,重在实在~,企业文化显而易见
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务