MST

星途 面试题库

面试题:C++ 原子操作与锁机制在对象间复杂数据结构共享并发问题中的权衡

在一个高度并发的系统中,有一个复杂的 C++ 对象,内部包含多种数据结构(如链表、哈希表等)需要在线程间共享。分析在这种情况下,使用原子操作(如 `std::atomic`)和锁机制(如 `std::mutex` 及其衍生类型)的优缺点,在不同的使用场景下应如何选择。并通过代码示例说明如何在实际场景中结合使用原子操作和锁机制,以达到最优的性能和线程安全性。
10.3万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

原子操作(std::atomic

  • 优点
    • 性能高:原子操作通常由硬件指令实现,开销小,在对单个变量进行简单读写操作时,性能优于锁机制,特别适合高并发场景。
    • 无死锁风险:因为不需要获取和释放锁,不存在死锁问题。
  • 缺点
    • 功能局限:只能对单个变量(或简单数据类型的聚合体)进行原子操作,对于复杂数据结构(如链表、哈希表),原子操作无法直接保证整体的一致性和完整性。
    • 编程复杂:需要精细控制,例如在实现复杂逻辑时,需要处理内存顺序等问题,增加编程难度。

锁机制(std::mutex 及其衍生类型)

  • 优点
    • 功能强大:能够保护任意复杂的数据结构和代码块,通过锁的获取和释放,保证数据在临界区的一致性和完整性。
    • 易于理解和使用:编程模型简单直观,对于开发人员来说易于上手。
  • 缺点
    • 性能开销:锁的获取和释放操作涉及系统调用,开销较大,在高并发场景下可能成为性能瓶颈。
    • 死锁风险:如果锁的使用不当,容易出现死锁情况,排查和解决死锁问题较为困难。

场景选择

  • 简单变量操作:如果只是对简单数据类型(如整数、指针)进行计数、标志位设置等简单操作,优先使用原子操作,以提高性能。
  • 复杂数据结构操作:对于复杂数据结构(如链表、哈希表),需要保证数据整体一致性和完整性时,使用锁机制更为合适。

代码示例

#include <iostream>
#include <mutex>
#include <atomic>
#include <thread>
#include <vector>

// 复杂对象
class ComplexObject {
private:
    std::atomic<int> count; // 可使用原子操作的简单成员
    std::mutex mtx;
    // 链表节点结构
    struct ListNode {
        int value;
        ListNode* next;
        ListNode(int val) : value(val), next(nullptr) {}
    };
    ListNode* head;

public:
    ComplexObject() : count(0), head(nullptr) {}

    // 使用原子操作增加计数
    void incrementCount() {
        ++count;
    }

    // 使用锁机制操作链表
    void addToList(int value) {
        std::lock_guard<std::mutex> lock(mtx);
        ListNode* newNode = new ListNode(value);
        newNode->next = head;
        head = newNode;
    }

    // 获取计数
    int getCount() const {
        return count.load();
    }

    // 打印链表
    void printList() const {
        std::lock_guard<std::mutex> lock(mtx);
        ListNode* current = head;
        while (current) {
            std::cout << current->value << " ";
            current = current->next;
        }
        std::cout << std::endl;
    }
};

int main() {
    ComplexObject obj;
    std::vector<std::thread> threads;

    // 启动多个线程操作对象
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back([&obj]() {
            obj.incrementCount();
            obj.addToList(i);
        });
    }

    // 等待所有线程完成
    for (auto& th : threads) {
        th.join();
    }

    // 输出结果
    std::cout << "Count: " << obj.getCount() << std::endl;
    std::cout << "List: ";
    obj.printList();

    return 0;
}

在上述代码中,count 成员变量使用 std::atomic 进行计数操作,利用了原子操作的高性能优势;而链表操作则使用 std::mutex 保护,确保链表结构在多线程环境下的一致性和完整性。这样结合使用原子操作和锁机制,既提高了性能,又保证了线程安全性。