MST

星途 面试题库

面试题:C++私有成员访问安全性与多线程环境

假设在一个多线程的C++程序中,有一个类C包含私有成员count。多个线程可能会同时尝试通过类C的公有成员函数来间接访问和修改count。请分析在这种情况下,可能出现的关于私有成员访问安全性的问题,并提出至少两种解决方案(如使用锁机制、原子操作等),说明每种方案的优缺点。
41.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

可能出现的问题

  1. 数据竞争:多个线程同时访问和修改count,由于CPU时间片的分配,可能导致数据不一致。例如一个线程读取count的值,另一个线程在其修改前也读取了相同的值,然后两个线程分别进行修改后写回,最终结果并非预期的连续两次修改。
  2. 未定义行为:如果在多线程环境下对count的访问和修改没有适当的同步机制,可能会导致未定义行为,程序可能出现崩溃或产生不可预测的结果。

解决方案

1. 使用互斥锁(Mutex)

  • 实现代码
#include <iostream>
#include <mutex>

class C {
private:
    int count;
    std::mutex mtx;
public:
    C() : count(0) {}

    void increment() {
        std::lock_guard<std::mutex> lock(mtx);
        ++count;
    }

    int getCount() {
        std::lock_guard<std::mutex> lock(mtx);
        return count;
    }
};
  • 优点
    • 简单直观,易于理解和实现。
    • 适用于多种场景,对数据访问的控制粒度可以根据需求灵活调整。
  • 缺点
    • 性能开销,加锁和解锁操作会带来一定的时间开销,尤其是在高并发场景下,频繁的加解锁可能会成为性能瓶颈。
    • 可能产生死锁,如果多个线程以不同顺序获取多个锁,可能会陷入死锁状态。

2. 使用原子操作(Atomic)

  • 实现代码
#include <iostream>
#include <atomic>

class C {
private:
    std::atomic<int> count;
public:
    C() : count(0) {}

    void increment() {
        ++count;
    }

    int getCount() {
        return count.load();
    }
};
  • 优点
    • 高效,原子操作由硬件直接支持,不需要像锁那样进行内核态的上下文切换,性能开销小。
    • 避免死锁,因为不需要像锁那样进行复杂的锁获取和释放顺序管理。
  • 缺点
    • 功能相对有限,只能对单个变量进行原子操作,对于复杂的数据结构或多个变量的组合操作难以直接支持。
    • 可移植性问题,不同平台对原子操作的支持细节可能有所不同,在跨平台开发时需要注意兼容性。

3. 使用读写锁(Read - Write Lock)

  • 实现代码
#include <iostream>
#include <shared_mutex>

class C {
private:
    int count;
    std::shared_mutex rwMutex;
public:
    C() : count(0) {}

    void increment() {
        std::unique_lock<std::shared_mutex> lock(rwMutex);
        ++count;
    }

    int getCount() {
        std::shared_lock<std::shared_mutex> lock(rwMutex);
        return count;
    }
};
  • 优点
    • 适用于读多写少的场景,读操作可以并发执行,提高了程序的并发性能。
    • 相比于普通互斥锁,在读写操作比例不均衡的情况下,性能更好。
  • 缺点
    • 实现相对复杂,需要区分读操作和写操作,并且要正确使用不同类型的锁。
    • 写操作仍然是独占的,如果写操作频繁,可能会导致读操作长时间等待。