MST

星途 面试题库

面试题:C++多线程环境下全局变量引用的安全考量

在一个多线程的C++程序里,存在一个全局变量`std::vector<int> globalVec;`,多个线程会对其进行读和写操作。请阐述你会采取哪些措施来确保对`globalVec`的引用是安全的,并分析不同措施在性能和复杂性方面的差异。
49.1万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

措施

  1. 互斥锁(std::mutex
    • 使用方式:在对globalVec进行读写操作前,先锁定互斥锁,操作完成后解锁。例如:
    std::mutex globalVecMutex;
    void writeToGlobalVec(int value) {
        std::lock_guard<std::mutex> lock(globalVecMutex);
        globalVec.push_back(value);
    }
    int readFromGlobalVec(size_t index) {
        std::lock_guard<std::mutex> lock(globalVecMutex);
        return globalVec[index];
    }
    
  2. 读写锁(std::shared_mutex C++17 及以上)
    • 使用方式:对于读操作,使用共享锁(std::shared_lock);对于写操作,使用独占锁(std::unique_lock)。例如:
    std::shared_mutex globalVecReadWriteMutex;
    void writeToGlobalVec(int value) {
        std::unique_lock<std::shared_mutex> lock(globalVecReadWriteMutex);
        globalVec.push_back(value);
    }
    int readFromGlobalVec(size_t index) {
        std::shared_lock<std::shared_mutex> lock(globalVecReadWriteMutex);
        return globalVec[index];
    }
    
  3. 原子操作(std::atomic 部分场景适用)
    • 使用方式:如果globalVec的操作主要是简单的追加或特定原子性支持的操作,可以考虑使用std::atomic相关特性。例如,如果只是简单地追加元素,可以自定义一个基于std::atomic<int>的索引来实现线程安全的追加(但这种方式对复杂的vector操作支持有限)。
    std::atomic<int> globalVecIndex(0);
    void writeToGlobalVec(int value) {
        int idx = globalVecIndex.fetch_add(1);
        globalVec[idx] = value;
    }
    

性能和复杂性差异

  1. 互斥锁
    • 性能:读写操作都需要获取独占锁,当有多个读操作时,即使它们之间不会相互影响,也会因为锁的独占性而串行化,性能相对较低,尤其是读操作频繁的场景。
    • 复杂性:实现相对简单,只需要一个互斥锁对象,在需要保护的代码段前后进行加锁和解锁操作。
  2. 读写锁
    • 性能:读操作可以并发执行,因为读锁是共享的,只有写操作需要独占锁。所以在多读少写的场景下,性能优于互斥锁。但在多写场景下,与互斥锁性能相近,因为写操作都需要独占锁。
    • 复杂性:比互斥锁略复杂,需要区分读锁(std::shared_lock)和写锁(std::unique_lock)的使用,并且需要理解共享锁和独占锁的机制。
  3. 原子操作
    • 性能:在特定操作下(如简单的原子性计数等)性能非常高,因为不需要像锁那样进行上下文切换等开销。但对于复杂的vector操作,如插入、删除元素等,原子操作难以满足需求,并且可能需要复杂的设计来模拟完整的vector行为。
    • 复杂性:实现复杂,对于std::vector这样复杂的数据结构,要使用原子操作保证线程安全,需要深入理解原子操作的原理和对vector底层机制的了解,并且往往只能实现部分功能。