多线程环境下向数组添加数据的结构时,我们需要考虑线程安全。一般有几种常见的设计方案:
方案 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(互斥锁)简单可靠。
- 高并发写入:方案 2(原子索引)性能更优。
- 允许最终一致性:方案 3(线程本地存储)避免锁争用。