使用无锁队列可以提高并发性能,避免传统锁机制的开销。下面是一个简单的无锁队列实现,以及在多线程环境中如何读取 result
的示例。
无锁队列的基本实现
以下是一个基于单向链表的无锁队列(Lock-Free Queue)示例:
#include <atomic>
#include <memory>
#include <iostream>
template<typename T>
class LockFreeQueue {
public:
LockFreeQueue() {
Node* node = new Node(); // 哨兵节点
head.store(node);
tail.store(node);
}
~LockFreeQueue() {
while (Node* node = head.load()) {
head.store(node->next.load());
delete node;
}
}
void Enqueue(T value) {
Node* newNode = new Node(value);
Node* oldTail = tail.load();
while (!tail.compare_exchange_weak(oldTail, newNode)) {
// 重试直到成功更新尾部指针
std::cout << "Retrying to enqueue..." << std::endl;
}
oldTail->next.store(newNode); // 将新节点链接到旧尾部
}
std::shared_ptr<T> Dequeue() {
Node* oldHead = head.load();
while (true) {
if (oldHead == tail.load()) return nullptr; // 队列为空
Node* next = oldHead->next.load();
if (head.compare_exchange_strong(oldHead, next)) {
// 更新头指针,如果成功则返回数据
std::shared_ptr<T> result(std::make_shared<T>(oldHead->data));
delete oldHead; // 释放旧头节点
return result;
}
// 如果失败,继续尝试获取新的头节点
}
}
private:
struct Node {
T data;
std::atomic<Node*> next;
explicit Node(T val) : data(val), next(nullptr) {}
Node() : next(nullptr) {} // 哨兵节点构造函数
};
std::atomic<Node*> head;
std::atomic<Node*> tail;
};
使用无锁队列
下面是如何在多个线程中使用这个无锁队列进行入队和出队操作的示例:
#include <thread>
#include <vector>
#include <iostream>
void producer(LockFreeQueue<int>& queue) {
for (int i = 0; i < 10; ++i) {
queue.Enqueue(i);
std::cout << "Produced: " << i << std::endl;
}
}
void consumer(LockFreeQueue<int>& queue) {
for (int i = 0; i < 10; ++i) {
auto value = queue.Dequeue();
if(value){
std::cout << "Consumed: " << *value << std::endl;
}else{
--i; // retry if nothing was consumed.
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 避免过快重试
}
}
}
int main() {
LockFreeQueue<int> queue;
std::thread prod(producer, std::ref(queue));
std::thread cons(consumer, std::ref(queue));
prod.join();
cons.join();
return 0;
}
总结
- Enqueue 和 Dequeue 方法均使用原子操作,确保在多线程环境下安全地更新头和尾指针。
- 为了避免内存泄漏,建议使用
std::shared_ptr
来管理元素。 - 在消费端,每次调用
Dequeue()
后会检查是否有值返回。如果没有值,它会重试几次。这种策略可以根据需求调整,比如使用条件变量来减少忙等待。
这种实现方式在高并发情况下表现良好,但设计复杂性增加,调试也较困难。在生产代码中使用时,要仔细考虑正确性、性能和可维护性。
内容由零声教学AI助手提供,问题来源于学员提问