MST

星途 面试题库

面试题:C++ 静态函数全局访问控制与多线程安全

考虑一个多线程环境下,类 `ThreadSafeClass` 有一个静态函数 `calculate`,该函数会修改类的静态成员变量。如何确保在多个线程同时调用 `calculate` 函数时,对静态成员变量的访问是线程安全的?请给出具体实现思路并说明在全局访问控制方面可能会遇到的问题及解决方案。
37.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

实现思路

  1. 使用互斥锁(Mutex)
    • 在C++ 中,可以使用 <mutex> 库中的 std::mutex。在 calculate 函数中,定义一个静态 std::mutex 对象,例如 static std::mutex mtx;
    • calculate 函数开始处,调用 mtx.lock() 来锁定互斥锁,这样其他线程调用 calculate 函数时,若互斥锁已被锁定,就会阻塞等待。
    • calculate 函数结束处,调用 mtx.unlock() 来释放互斥锁,允许其他线程获取锁并进入 calculate 函数。
    • 示例代码如下:
#include <mutex>

class ThreadSafeClass {
private:
    static int staticMember;
    static std::mutex mtx;
public:
    static void calculate() {
        mtx.lock();
        // 修改静态成员变量
        staticMember++;
        mtx.unlock();
    }
};

int ThreadSafeClass::staticMember = 0;
std::mutex ThreadSafeClass::mtx;
  1. 使用读写锁(Read - Write Lock)
    • 如果对静态成员变量的操作大多是读操作,少量是写操作,可以使用读写锁提高性能。在C++ 中,可以使用 <shared_mutex> 库中的 std::shared_mutex
    • 读操作时,使用 std::shared_lock<std::shared_mutex> 来获取共享锁,允许多个线程同时进行读操作。
    • 写操作时,使用 std::unique_lock<std::shared_mutex> 来获取独占锁,此时其他线程无论是读还是写操作都会被阻塞。
    • 示例代码如下:
#include <shared_mutex>

class ThreadSafeClass {
private:
    static int staticMember;
    static std::shared_mutex rwMutex;
public:
    static void readCalculate() {
        std::shared_lock<std::shared_mutex> lock(rwMutex);
        // 读取静态成员变量
        int value = staticMember;
    }
    static void writeCalculate() {
        std::unique_lock<std::shared_mutex> lock(rwMutex);
        // 修改静态成员变量
        staticMember++;
    }
};

int ThreadSafeClass::staticMember = 0;
std::shared_mutex ThreadSafeClass::rwMutex;

全局访问控制方面可能遇到的问题及解决方案

  1. 死锁问题
    • 问题:当多个线程需要获取多个锁时,如果获取锁的顺序不一致,可能会导致死锁。例如,线程A获取锁1,然后尝试获取锁2,而线程B获取锁2,然后尝试获取锁1,此时两个线程都在等待对方释放锁,就会陷入死锁。
    • 解决方案
      • 规定锁的获取顺序:在整个程序中,规定获取锁的统一顺序。例如,总是先获取锁1,再获取锁2,这样可以避免死锁。
      • 使用 std::lock:C++ 提供了 std::lock 函数,它可以一次性获取多个锁,并且保证不会发生死锁。例如 std::lock(mutex1, mutex2);,该函数会以一种无死锁的方式获取 mutex1mutex2
  2. 性能问题
    • 问题:如果互斥锁保护的代码段过长,会导致其他线程等待时间过长,降低系统的并发性能。例如,在 calculate 函数中,除了修改静态成员变量外,还有大量其他无关的计算操作也被锁保护,这会影响性能。
    • 解决方案
      • 缩小锁的保护范围:将不需要线程安全的操作移出锁保护的代码段。例如,将无关的计算操作放在获取锁之前或释放锁之后执行。
      • 使用更细粒度的锁:如果静态成员变量可以分成几个独立的部分进行操作,可以为每个部分使用单独的锁,这样不同线程可以同时操作不同部分,提高并发性能。
  3. 锁争用问题
    • 问题:当大量线程频繁访问 calculate 函数时,锁争用会非常激烈,导致系统开销增大,性能下降。
    • 解决方案
      • 使用无锁数据结构:如果静态成员变量的操作可以使用无锁数据结构(如无锁队列、无锁哈希表等)来实现,就可以避免锁争用问题。无锁数据结构利用原子操作来保证数据的一致性。
      • 减少线程数量:合理控制并发线程的数量,避免过多线程竞争锁。可以根据系统资源(如CPU核心数)来动态调整线程数量。