MST

星途 面试题库

面试题:C++ 多线程下全局变量的复杂场景及优化策略

假设在一个C++多线程应用程序中,有一个复杂的全局数据结构(例如自定义的链表类`class MyLinkedList`),多个线程会对这个链表进行插入、删除和遍历操作。请详细阐述在这种场景下可能出现的所有与全局变量相关的问题,包括但不限于同步问题、内存管理问题、性能瓶颈等。并提出一套完整的优化策略,包括使用合适的锁机制、无锁数据结构或其他高级技术,并给出核心代码示例及优化前后的性能对比分析思路。
21.0万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

可能出现的问题

  1. 同步问题
    • 数据竞争:多个线程同时对链表进行插入、删除操作时,可能导致数据不一致。例如,一个线程在插入节点时,另一个线程同时删除同一个节点,会使链表结构遭到破坏。
    • 死锁:如果在获取锁的顺序上设计不当,例如线程A获取锁1后等待锁2,而线程B获取锁2后等待锁1,就会发生死锁。
  2. 内存管理问题
    • 悬空指针:当一个线程删除节点后释放内存,其他线程可能仍然持有指向该已释放内存的指针,导致悬空指针,后续访问该指针会引发未定义行为。
    • 内存泄漏:如果在多线程环境下,节点的内存释放逻辑有误,例如某个线程插入节点后没有正确释放,可能导致内存泄漏。
  3. 性能瓶颈
    • 锁竞争:如果使用锁机制,多个线程频繁竞争锁,会导致大量线程等待,降低系统的并发性能。
    • 频繁上下文切换:锁竞争会导致线程频繁进入等待状态,从而引发操作系统的上下文切换,增加额外开销。

优化策略

  1. 锁机制
    • 互斥锁(std::mutex):最基本的锁类型,通过在对链表操作前后加锁解锁来保证数据一致性。
    • 读写锁(std::shared_mutex):如果遍历操作远多于插入删除操作,可以使用读写锁。读操作时可以多个线程同时进行,写操作时则独占锁,避免写 - 写和读 - 写冲突。
  2. 无锁数据结构
    • 无锁链表:利用原子操作实现无锁数据结构,避免锁竞争。例如使用std::atomic类型来管理链表节点指针,保证对指针的操作是原子的。
  3. 其他高级技术
    • 线程本地存储(TLS):对于一些与链表操作相关的临时数据,可以使用线程本地存储,减少线程间的数据竞争。

核心代码示例

  1. 使用互斥锁
#include <iostream>
#include <mutex>
#include <thread>

class MyLinkedList {
private:
    struct Node {
        int data;
        Node* next;
        Node(int d) : data(d), next(nullptr) {}
    };
    Node* head;
    std::mutex listMutex;

public:
    MyLinkedList() : head(nullptr) {}

    void insert(int data) {
        std::lock_guard<std::mutex> lock(listMutex);
        Node* newNode = new Node(data);
        newNode->next = head;
        head = newNode;
    }

    void remove(int data) {
        std::lock_guard<std::mutex> lock(listMutex);
        Node* current = head;
        Node* prev = nullptr;
        while (current != nullptr && current->data != data) {
            prev = current;
            current = current->next;
        }
        if (current != nullptr) {
            if (prev == nullptr) {
                head = current->next;
            } else {
                prev->next = current->next;
            }
            delete current;
        }
    }

    void traverse() {
        std::lock_guard<std::mutex> lock(listMutex);
        Node* current = head;
        while (current != nullptr) {
            std::cout << current->data << " ";
            current = current->next;
        }
        std::cout << std::endl;
    }
};

int main() {
    MyLinkedList list;
    std::thread t1([&list]() { list.insert(1); });
    std::thread t2([&list]() { list.insert(2); });
    t1.join();
    t2.join();
    list.traverse();
    return 0;
}
  1. 使用无锁链表(简化示例)
#include <iostream>
#include <atomic>
#include <thread>

class MyLockFreeLinkedList {
private:
    struct Node {
        int data;
        std::atomic<Node*> next;
        Node(int d) : data(d), next(nullptr) {}
    };
    std::atomic<Node*> head;

public:
    MyLockFreeLinkedList() : head(nullptr) {}

    void insert(int data) {
        Node* newNode = new Node(data);
        Node* oldHead;
        do {
            oldHead = head.load();
            newNode->next.store(oldHead);
        } while (!head.compare_exchange_weak(oldHead, newNode));
    }

    bool remove(int data) {
        Node* current = head.load();
        Node* prev = nullptr;
        while (current != nullptr && current->data != data) {
            prev = current;
            current = current->next.load();
        }
        if (current == nullptr) {
            return false;
        }
        if (prev == nullptr) {
            return head.compare_exchange_weak(current, current->next.load());
        } else {
            return prev->next.compare_exchange_weak(current, current->next.load());
        }
    }

    void traverse() {
        Node* current = head.load();
        while (current != nullptr) {
            std::cout << current->data << " ";
            current = current->next.load();
        }
        std::cout << std::endl;
    }
};

int main() {
    MyLockFreeLinkedList list;
    std::thread t1([&list]() { list.insert(1); });
    std::thread t2([&list]() { list.insert(2); });
    t1.join();
    t2.join();
    list.traverse();
    return 0;
}

性能对比分析思路

  1. 测试场景:设计多种测试场景,例如不同线程数量下对链表进行大量插入、删除、遍历操作。
  2. 性能指标
    • 时间:记录完成一定数量操作的总时间,例如使用std::chrono库来精确测量时间。
    • 吞吐量:计算单位时间内完成的操作数量。
  3. 分析方法
    • 绘制图表,对比使用不同优化策略(如互斥锁、读写锁、无锁数据结构)时的性能指标,观察锁竞争、上下文切换等因素对性能的影响。
    • 在不同硬件环境和操作系统下进行测试,以全面评估优化策略的有效性。