You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让类std::vector的C++自定义动态数组实现线程安全?

针对C++可扩容数组的多线程无锁读解决方案

给你几个实用的方案,都是针对扩容极少、不想每次读都加锁的场景设计的:

1. 原子指针间接层(最常用的轻量方案)

核心思路是用一个原子指针指向当前的数组缓冲区,读操作只需要原子加载这个指针,之后直接访问缓冲区元素;扩容时才加锁,完成新缓冲区分配和数据复制后,原子替换指针。

关键实现代码片段:

#include <atomic>
#include <mutex>

template<typename T>
class LockFreeVector {
private:
    std::atomic<T*> m_data = nullptr;
    size_t m_capacity = 0;
    std::atomic<size_t> m_size = 0;
    std::mutex m_expand_mutex; // 仅扩容时用

public:
    // 读操作:完全无锁
    T& operator[](size_t idx) {
        // 原子加载当前缓冲区指针,memory_order_acquire保证后续读可见
        T* current_data = m_data.load(std::memory_order_acquire);
        if (idx >= m_size.load(std::memory_order_relaxed)) {
            throw std::out_of_range("Index out of bounds");
        }
        return current_data[idx];
    }

    // 扩容操作:仅这里加锁
    void reserve(size_t new_capacity) {
        std::lock_guard<std::mutex> lock(m_expand_mutex);
        if (new_capacity <= m_capacity) return;

        T* new_data = new T[new_capacity];
        T* old_data = m_data.load();
        // 复制旧数据
        for (size_t i = 0; i < m_size; ++i) {
            new_data[i] = std::move(old_data[i]);
        }

        // 原子替换指针,memory_order_release保证之前的写对其他线程可见
        m_data.store(new_data, std::memory_order_release);
        m_capacity = new_capacity;
        // 若存在长耗时读操作,需延迟释放旧内存,可搭配引用计数或epoch回收机制
        delete[] old_data;
    }

    // 增删改操作略,需同步时可复用m_expand_mutex或单独加锁
};

2. 预分配足够容量(最简单的方案)

如果能提前预估业务的最大元素数量,直接在容器初始化时预分配足够的capacity,彻底禁用自动扩容逻辑。这样数组缓冲区永远不会移动,读操作完全不需要任何同步(元素的增删改仍需同步,但和扩容无关)。

示例:

CustomVector<int> vec;
vec.reserve(10000); // 预分配足够容纳10000个元素的空间
// 后续操作不会触发扩容,读元素直接访问即可

这个方案零额外开销、实现最简单,但仅适合业务场景明确、容量可预估的情况。

3. 读写锁(shared_mutex)实现

用C++17提供的std::shared_mutex(或Boost的shared_mutex),读操作加共享锁(多线程可同时持有),扩容等写操作加独占锁(同一时间仅一个线程持有)。

代码示例:

#include <shared_mutex>

template<typename T>
class SharedVector {
private:
    T* m_data = nullptr;
    size_t m_capacity = 0;
    size_t m_size = 0;
    std::shared_mutex m_mutex;

public:
    // 读操作:加共享锁,开销远小于独占锁
    const T& operator[](size_t idx) const {
        std::shared_lock<std::shared_mutex> lock(m_mutex);
        if (idx >= m_size) throw std::out_of_range("Index out of bounds");
        return m_data[idx];
    }

    // 扩容操作:加独占锁
    void expand() {
        std::unique_lock<std::shared_mutex> lock(m_mutex);
        size_t new_capacity = m_capacity == 0 ? 4 : m_capacity * 2;
        T* new_data = new T[new_capacity];
        for (size_t i = 0; i < m_size; ++i) {
            new_data[i] = std::move(m_data[i]);
        }
        delete[] m_data;
        m_data = new_data;
        m_capacity = new_capacity;
    }
};

这个方案实现简单,读操作开销极低,无需处理复杂的内存回收问题,适合不想搞复杂逻辑的场景。

4. 稳定缓冲区设计(最灵活但复杂)

把容器的元数据(大小、容量)和缓冲区分离,缓冲区一旦分配就不再移动。扩容时分配新缓冲区,旧缓冲区标记为只读,后续读操作可继续访问旧缓冲区,写操作只用新缓冲区;同时用引用计数跟踪旧缓冲区的访问次数,等所有读操作完成后再释放旧内存。

这个方案读操作完全无锁,扩容也不会阻塞读,但实现复杂度较高,适合对性能要求极高的场景。

内容的提问来源于stack exchange,提问作者Zebrafish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 15:42:47