MST

星途 面试题库

面试题:C++ 循环链表在多线程环境下的应用

在多线程环境下,设计并实现一个基于C++ 循环链表的数据结构,要求该结构支持线程安全的插入、删除和遍历操作。描述你的设计思路,包括如何处理锁机制以避免死锁和竞争条件。请给出关键代码片段,并分析在高并发场景下该设计的性能瓶颈及可能的优化方向。
16.1万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 数据结构设计
    • 定义循环链表节点结构,包含数据成员和指向下一个节点的指针。
    • 循环链表包含头指针(也可视为尾指针,因为是循环的)。
  2. 锁机制
    • 使用互斥锁(std::mutex)来保护对链表的操作。
    • 在每个可能引发竞争条件的操作(插入、删除、遍历)前加锁,操作完成后解锁。
    • 为避免死锁,采用固定的加锁顺序。例如,在涉及多个锁的操作中,总是先获取链表锁,再获取其他可能需要的锁(如果有)。

关键代码片段

#include <iostream>
#include <mutex>

// 定义链表节点
template <typename T>
struct ListNode {
    T data;
    ListNode* next;
    ListNode(const T& value) : data(value), next(nullptr) {}
};

// 定义循环链表
template <typename T>
class CircularList {
private:
    ListNode<T>* head;
    std::mutex listMutex;

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

    // 线程安全的插入操作
    void insert(const T& value) {
        std::lock_guard<std::mutex> guard(listMutex);
        ListNode<T>* newNode = new ListNode<T>(value);
        if (!head) {
            head = newNode;
            head->next = head;
        } else {
            ListNode<T>* current = head;
            while (current->next != head) {
                current = current->next;
            }
            current->next = newNode;
            newNode->next = head;
        }
    }

    // 线程安全的删除操作
    bool remove(const T& value) {
        std::lock_guard<std::mutex> guard(listMutex);
        if (!head) return false;
        ListNode<T>* current = head;
        ListNode<T>* prev = nullptr;
        do {
            if (current->data == value) {
                if (prev) {
                    prev->next = current->next;
                } else {
                    // 如果要删除的是头节点
                    if (current->next == head) {
                        delete current;
                        head = nullptr;
                    } else {
                        head = current->next;
                        ListNode<T>* last = head;
                        while (last->next != current) {
                            last = last->next;
                        }
                        last->next = head;
                    }
                }
                delete current;
                return true;
            }
            prev = current;
            current = current->next;
        } while (current != head);
        return false;
    }

    // 线程安全的遍历操作
    void traverse() {
        std::lock_guard<std::mutex> guard(listMutex);
        if (!head) return;
        ListNode<T>* current = head;
        do {
            std::cout << current->data << " ";
            current = current->next;
        } while (current != head);
        std::cout << std::endl;
    }

    ~CircularList() {
        std::lock_guard<std::mutex> guard(listMutex);
        if (head) {
            ListNode<T>* current = head;
            ListNode<T>* next;
            do {
                next = current->next;
                delete current;
                current = next;
            } while (current != head);
        }
    }
};

性能瓶颈及优化方向

性能瓶颈

  1. 锁粒度大:整个链表使用一个互斥锁,在高并发场景下,大量线程可能因等待锁而阻塞,导致并发性能下降。
  2. 遍历效率低:遍历链表时需要顺序访问每个节点,在链表较长时,遍历操作会成为性能瓶颈。

优化方向

  1. 减小锁粒度
    • 可以采用读写锁(std::shared_mutex),读操作可以并发执行,只有写操作(插入、删除)需要独占锁。
    • 或者将链表划分为多个部分,每个部分使用独立的锁,这样不同部分的操作可以并发执行。
  2. 优化遍历
    • 可以在链表中维护额外的信息,如链表长度,以便在某些操作中减少不必要的遍历。
    • 对于读操作频繁的场景,可以考虑使用无锁数据结构(如无锁链表)来提高并发性能。