C++ 多线程与并发面试题

1. 进程和线程的区别?

答案:

  • 进程资源分配的基本单位独立的地址空间进程间通信(IPC)开销大创建销毁开销大
  • 线程CPU调度的基本单位共享进程的地址空间线程间通信简单(共享内存)创建销毁开销小
  • 对比进程更安全,隔离性好线程更轻量,切换快线程共享资源,需要同步
  • C++中的线程C++11引入std::thread跨平台的线程库RAII风格管理

2. 什么是线程安全?如何实现?

答案:

  • 定义多线程访问时,程序行为正确不会出现数据竞争结果可预测
  • 实现方法互斥锁(Mutex)保护临界区同一时间只有一个线程访问读写锁多个读者,一个写者提高并发度原子操作不可分割的操作无需加锁无锁数据结构使用CAS(Compare-And-Swap)高性能
  • 线程安全的类
class ThreadSafeCounter {
    mutable mutex mtx;
    int value;
public:
    void increment() {
        lock_guard<mutex> lock(mtx);
        value++;
    }
    
    int get() const {
        lock_guard<mutex> lock(mtx);
        return value;
    }
};

3. 什么是死锁?如何避免?

答案:

  • 死锁定义两个或多个线程互相等待对方释放资源导致程序永久阻塞
  • 死锁的四个必要条件互斥:资源不能共享持有并等待:持有资源同时等待其他资源不可抢占:资源不能被强制释放循环等待:存在资源等待环路
  • 避免死锁固定加锁顺序
// 总是先锁id小的
void transfer(Account& from, Account& to, int amount) {
    if (&from < &to) {
        lock_guard<mutex> lock1(from.mtx);
        lock_guard<mutex> lock2(to.mtx);
        // 转账
    } else {
        lock_guard<mutex> lock1(to.mtx);
        lock_guard<mutex> lock2(from.mtx);
        // 转账
    }
}
  • 使用std::lock同时锁定
void transfer(Account& from, Account& to, int amount) {
    lock(from.mtx, to.mtx);
    lock_guard<mutex> lock1(from.mtx, adopt_lock);
    lock_guard<mutex> lock2(to.mtx, adopt_lock);
    // 转账
}
  • 使用超时机制
  • 避免嵌套锁

4. mutex、lock_guard、unique_lock的区别?

答案:

  • mutex基本互斥锁需要手动lock/unlock容易忘记解锁
mutex mtx;
mtx.lock();
// 临界区
mtx.unlock();
  • lock_guardRAII风格构造时加锁,析构时解锁不能手动解锁轻量级
{
    lock_guard<mutex> lock(mtx);
    // 临界区
}  // 自动解锁
  • unique_lock更灵活的RAII可以手动lock/unlock可以转移所有权可以延迟加锁配合条件变量使用
unique_lock<mutex> lock(mtx, defer_lock);
// 做其他事
lock.lock();
// 临界区
lock.unlock();
// 做其他事
  • 选择建议简单场景用lock_guard需要灵活控制用unique_lock条件变量必须用unique_lock

5. 什么是条件变量?如何使用?

答案:

  • 定义线程间同步机制等待某个条件成立避免忙等待
  • 基本使用
mutex mtx;
condition_variable cv;
queue<int> data_queue;

// 生产者
void producer() {
    for (int i = 0; i < 10; i++) {
        {
            lock_guard<mutex> lock(mtx);
            data_queue.push(i);
        }
        cv.notify_one();  // 通知消费者
    }
}

// 消费者
void consumer() {
    while (true) {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, []{ return !data_queue.empty(); });
        int value = data_queue.front();
        data_queue.pop();
        lock.unlock();
        // 处理value
    }
}
  • APIwait:等待通知notify_one:唤醒一个等待线程notify_all:唤醒所有等待线程
  • 注意事项必须配合unique_lock使用使用谓词避免虚假唤醒修改条件前加锁

6. 什么是原子操作?

答案:

  • 定义不可分割的操作不会被线程切换打断无需加锁
  • std::atomic

 

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

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

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

全部评论

相关推荐

评论
点赞
3
分享

创作者周榜

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