多线程写数组的设计方案

多线程环境下向数组添加数据的结构时,我们需要考虑线程安全。一般有几种常见的设计方案:

方案 1:互斥锁保护动态数组

实现思路:使用互斥锁(std::mutex)保护对数组的每次操作,确保同一时间只有一个线程修改数组。这个方案是最简单最常见的,编写代码也容易。

代码示例:

#include <vector>
#include <mutex>

class ThreadSafeVector {
private:
    std::vector<int> data;
    std::mutex mtx;

public:
    void add(int value) {
        std::lock_guard<std::mutex> lock(mtx);
        data.push_back(value);
    }
};

优点:简单易实现,直接利用标准库。

缺点:高并发下锁竞争可能成为性能瓶颈。

方案 2:原子索引 + 预分配内存

什么是原子索引?

原子索引是一种在多线程环境下,通过原子操作管理共享资源(如数组写入位置)的技术。它的核心思想是使用原子变量(如 std::atomic)记录当前可写入的位置索引,多个线程通过原子操作安全地竞争索引,避免数据竞争(Data Race)。

实现思路:预分配固定大小的数组,通过原子变量管理当前写入位置索引。仅当空间不足时,通过锁动态扩容。

代码示例:

#include <vector>
#include <atomic>
#include <mutex>

class ConcurrentArray {
private:
    std::vector<int> data;
    std::atomic<size_t> index{0};
    std::mutex mtx;
    static const size_t INIT_SIZE = 1024;

public:
    ConcurrentArray() {
        data.resize(INIT_SIZE);
    }

    void add(int value) {
        size_t idx = index.fetch_add(1, std::memory_order_relaxed);
        if (idx < data.size()) {
            data[idx] = value;
        } else {
            // 处理扩容
            std::lock_guard<std::mutex> lock(mtx);
            if (idx >= data.size()) {
                data.resize(data.size() * 2);
            }
            data[idx] = value;
        }
    }
};

优点:大部分写入操作无锁,性能较高。

缺点:预分配可能导致内存浪费,动态扩容时仍需加锁。

方案 3:线程本地存储(Thread-Local Storage)

什么是线程本地存储?

线程本地存储(TLS)是一种允许每个线程拥有独立数据副本的机制。在多线程环境中,每个线程操作自己的本地数据,无需与其他线程竞争共享资源,从而完全避免锁的使用。当需要全局汇总数据时,再通过同步机制(如锁)合并各线程的本地数据。

实现思路:每个线程使用本地数组缓存数据,定期将数据合并到全局数组。

代码示例:

#include <vector>
#include <mutex>
#include <thread>

class ThreadLocalArray {
private:
    thread_local static std::vector<int> local_data;
    std::vector<int> global_data;
    std::mutex mtx;

public:
    void add(int value) {
        local_data.push_back(value);
        if (local_data.size() >= 100) {  // 定期合并
            merge();
        }
    }

    void merge() {
        std::lock_guard<std::mutex> lock(mtx);
        global_data.insert(global_data.end(), local_data.begin(), local_data.end());
        local_data.clear();
    }
};

优点:完全无锁写入,合并时才需同步。

缺点:数据访问延迟,合并时可能阻塞。

选择建议

  1. 低竞争场景:方案 1(互斥锁)简单可靠。
  2. 高并发写入:方案 2(原子索引)性能更优。
  3. 允许最终一致性:方案 3(线程本地存储)避免锁争用。

   转载规则


《多线程写数组的设计方案》 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
C++ 无锁编程原理 C++ 无锁编程原理
无锁编程(Lock-Free Programming)是现代并发编程中的一种重要技术,它避免了传统的锁机制,如互斥锁(mutex),并通过原子操作确保线程安全。无锁编程能够在多线程环境中显著提高程序的并发性和可伸缩性,减少线程之间的竞争、阻
2020-07-09
下一篇 
Linux 动态链接与静态链接 Linux 动态链接与静态链接
库是写好的、可复用的代码。现实中每个程序都要依赖很多底层库,不可能从零开始。本质上库是一种可执行代码的二进制形式,可被操作系统载入内存执行。库有两种:静态库(.a/.lib)和动态库(.so/.dll)——Windows 上是 .lib/.
2020-06-21
  目录