在C++多线程编程中,当多个线程需要同时操作同一个数据结构时,普通的数据结构由于没有内置的同步机制,很容易出现数据竞争、状态不一致等问题。并发数据结构就是为解决这类问题而设计的,它是一种支持多线程安全并发访问的数据结构,内部通过同步机制保证操作的原子性和可见性,不需要外部额外加锁就能安全使用。

并发数据结构的核心特性
和普通数据结构相比,并发数据结构需要满足几个核心特性:
- 线程安全:多个线程同时调用其接口时,不会出现数据竞争,最终状态和单线程顺序执行的结果一致。
- 并发友好:尽量减少线程间的阻塞,提升多线程场景下的访问性能,避免不必要的锁竞争。
- 接口明确:提供的操作接口语义清晰,调用者不需要了解内部同步细节就能正确使用。
常见的C++并发数据结构实现方式
1. 基于互斥锁的封装实现
这是最常见的实现方式,通过互斥锁保护内部数据,所有公开接口在操作前先加锁,操作完成后解锁。这种方式实现简单,适合大部分场景。
下面是一个简单的并发队列实现示例:
#include <queue>
#include <mutex>
#include <condition_variable>
template <typename T>
class ConcurrentQueue {
private:
std::queue<T> data_queue;
mutable std::mutex mtx;
std::condition_variable data_cond;
public:
// 入队操作
void push(T new_value) {
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(std::move(new_value));
data_cond.notify_one();
}
// 尝试出队,队列为空时返回false
bool try_pop(T& value) {
std::lock_guard<std::mutex> lock(mtx);
if (data_queue.empty()) {
return false;
}
value = std::move(data_queue.front());
data_queue.pop();
return true;
}
// 阻塞出队,队列为空时等待
void wait_and_pop(T& value) {
std::unique_lock<std::mutex> lock(mtx);
data_cond.wait(lock, [this] { return !data_queue.empty(); });
value = std::move(data_queue.front());
data_queue.pop();
}
// 判断队列是否为空
bool empty() const {
std::lock_guard<std::mutex> lock(mtx);
return data_queue.empty();
}
};
2. 基于原子操作的无锁实现
无锁并发数据结构不使用互斥锁,而是通过原子操作保证操作的线程安全,性能通常更好,但是实现复杂度更高。C++11之后提供了<atomic>头文件,支持各类原子操作。
下面是一个简单的无锁栈的简化实现示例:
#include <atomic>
#include <memory>
template <typename T>
class LockFreeStack {
private:
struct Node {
T data;
Node* next;
Node(T const& data_) : data(data_), next(nullptr) {}
};
std::atomic<Node*> head;
public:
LockFreeStack() : head(nullptr) {}
~LockFreeStack() {
while (pop()) {} // 清空栈
}
// 入栈操作
void push(T const& data) {
Node* new_node = new Node(data);
new_node->next = head.load(std::memory_order_relaxed);
// 循环尝试将新节点设置为头节点,直到成功
while (!head.compare_exchange_weak(
new_node->next, new_node,
std::memory_order_release,
std::memory_order_relaxed)) {}
}
// 出栈操作,栈为空时返回false
bool pop() {
Node* old_head = head.load(std::memory_order_relaxed);
// 循环尝试将头节点替换为下一个节点,直到成功
while (old_head &&
!head.compare_exchange_weak(
old_head, old_head->next,
std::memory_order_release,
std::memory_order_relaxed)) {}
if (old_head) {
delete old_head;
return true;
}
return false;
}
};
标准库中的并发数据结构支持
C++标准库本身没有内置太多现成的并发数据结构,但是提供了基础组件方便开发者实现。比如std::mutex、std::shared_mutex用于互斥访问控制,std::atomic用于原子操作,std::future、std::promise用于线程间异步结果传递。如果需要现成的并发数据结构,也可以借助第三方库,比如Intel TBB、Boost.Lockfree等,这些库提供了经过验证的并发队列、并发哈希表等常用结构。
使用并发数据结构的注意事项
- 不要对并发数据结构的所有操作都加粗粒度的锁,尽量缩小锁的范围,提升并发性能。
- 无锁结构虽然性能好,但是实现难度高,容易出现内存回收、ABA问题等隐藏bug,没有足够经验的情况下优先选择基于互斥锁的实现。
- 确认并发数据结构的接口是否满足自己的使用场景,比如是否需要阻塞等待、是否需要支持遍历操作等。
- 避免将并发数据结构的内部引用或指针暴露给外部,防止外部绕过同步机制直接修改数据。